This project is about to mine new small business client leads to a Bank based on the features of already acquired small business clients.

Initial General Setup

notebook setup

knitr::opts_chunk$set(tidy=TRUE,options(warn=1),cache=TRUE)
chooseCRANmirror(graphics=FALSE,ind=1)
knitr::opts_knit$set(root.dir=getwd())

cleaning the workplace

rm(list = ls())
gc(reset = TRUE)
          used (Mb) gc trigger (Mb) max used (Mb)
Ncells  582187 31.1     940480 50.3   582187 31.1
Vcells 1417360 10.9    2377228 18.2  1417360 10.9
unlink(paste(getwd(), '/Client_Acquisition_for_a_Bank.nb.html', sep = ''), recursive = TRUE)
unlink(paste(getwd(), '/_results/BankiUgyfelModel', sep = ''), recursive = TRUE)
unlink(paste(getwd(), '/_results/BankiUgyfelModel.java', sep = ''), recursive = TRUE)
unlink(paste(getwd(), '/_results/h2o-genmodel.jar', sep = ''), recursive = TRUE)

setting general options

Sys.getlocale()
[1] "LC_CTYPE=en_US.UTF-8;LC_NUMERIC=C;LC_TIME=en_US.UTF-8;LC_COLLATE=en_US.UTF-8;LC_MONETARY=en_US.UTF-8;LC_MESSAGES=en_US.UTF-8;LC_PAPER=en_US.UTF-8;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=en_US.UTF-8;LC_IDENTIFICATION=C"
Sys.setlocale('LC_ALL','C') 
[1] "LC_CTYPE=C;LC_NUMERIC=C;LC_TIME=C;LC_COLLATE=C;LC_MONETARY=C;LC_MESSAGES=en_US.UTF-8;LC_PAPER=en_US.UTF-8;LC_NAME=C;LC_ADDRESS=C;LC_TELEPHONE=C;LC_MEASUREMENT=en_US.UTF-8;LC_IDENTIFICATION=C"
options(StringAsFactor = FALSE)
print(paste('You are in', getwd(), 'directory. The intermediate materials and results will be placed here.', sep = ' '))
[1] "You are in /home/sbudai/Documents/projects/GitHub/Client_Acquisition_for_a_Bank directory. The intermediate materials and results will be placed here."

creating necessary subfolders

dir.create(file.path(getwd(), '_tmp'), showWarnings = FALSE)
dir.create(file.path(getwd(), '_results'), showWarnings = FALSE)

calling ‘install.load’ library and installing if required It contains a cool function for package installing/loading.

if(!'install.load' %in% rownames(installed.packages())) {
  install.packages('install.load')
  Sys.sleep(6)
}
library(install.load)

calling all the other necessary libraries and installing if required

install_load('formatR',
             'data.table',
             'sqldf', 
             'stringi', 
             'validate', 
             'ggplot2',
             'ggthemes',
             'simputation', 
             'knitr', 
             'xml2', 
             'reshape2',
             'XML',
             'pander',
             'Imap',
             'igraph',
             'ggmap',
             'randomForest',
             'missForest',
             'h2o',
             'grid',
             'plotly',
             'htmlwidgets',
             'openxlsx',
             'RSelenium')
data.table 1.10.4
  The fastest way to learn (by data.table authors): https://www.datacamp.com/courses/data-analysis-the-data-table-way
  Documentation: ?data.table, example(data.table) and browseVignettes("data.table")
  Release notes, videos and slides: http://r-datatable.com
Loading required package: gsubfn
Loading required package: proto
Loading required package: RSQLite

Attaching package: 'reshape2'

The following objects are masked from 'package:data.table':

    dcast, melt


Attaching package: 'igraph'

The following object is masked from 'package:validate':

    compare

The following objects are masked from 'package:stats':

    decompose, spectrum

The following object is masked from 'package:base':

    union

Google Maps API Terms of Service: http://developers.google.com/maps/terms.
Please cite ggmap if you use it: see citation('ggmap') for details.
randomForest 4.6-12
Type rfNews() to see new features/changes/bug fixes.

Attaching package: 'randomForest'

The following object is masked from 'package:simputation':

    na.roughfix

The following object is masked from 'package:ggplot2':

    margin

Loading required package: foreach
foreach: simple, scalable parallel programming from Revolution Analytics
Use Revolution R for scalability, fault tolerance and more.
http://www.revolutionanalytics.com
Loading required package: itertools
Loading required package: iterators

----------------------------------------------------------------------

Your next step is to start H2O:
    > h2o.init()

For H2O package documentation, ask for help:
    > ??h2o

After starting H2O, you can use the Web UI at http://localhost:54321
For more information visit http://docs.h2o.ai

----------------------------------------------------------------------


Attaching package: 'h2o'

The following objects are masked from 'package:data.table':

    hour, month, week, year

The following objects are masked from 'package:stats':

    cor, sd, var

The following objects are masked from 'package:base':

    %*%, %in%, &&, apply, as.factor, as.numeric, colnames, colnames<-, ifelse, is.character, is.factor,
    is.numeric, log, log10, log1p, log2, round, signif, trunc, ||


Attaching package: 'plotly'

The following object is masked from 'package:ggmap':

    wind

The following objects are masked from 'package:igraph':

    %>%, groups

The following object is masked from 'package:ggplot2':

    last_plot

The following object is masked from 'package:stats':

    filter

The following object is masked from 'package:graphics':

    layout
Sys.sleep(3)
keys <- data.table(fread('~/Documents/creds.csv'))
keys <- keys[ProjectTitle == 'Client_Acquisition_for_a_Bank', (2:3), with = FALSE]

creating function to display graphs on one page

# http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2)/
## Multiple plot function
##
## ggplot objects can be passed in ..., or to plotlist (as a list of ggplot objects)
## - cols:   Number of columns in layout
## - layout: A matrix specifying the layout. If present, 'cols' is ignored.
##
## If the layout is something like matrix(c(1,2,3,3), nrow=2, byrow=TRUE),
## then plot 1 will go in the upper left, 2 will go in the upper right, and
## 3 will go all the way across the bottom.
####
#  multiplot <- function(..., plotlist = NULL, file, cols = 1, layout = NULL) {
#  # Make a list from the ... arguments and plotlist
#  plots <- c(list(...), plotlist)
#  numPlots = length(plots)
#  # If layout is NULL, then use 'cols' to determine layout
#  if (is.null(layout)) {
#    # Make the panel
#    # ncol: Number of columns of plots
#    # nrow: Number of rows needed, calculated from # of cols
#    layout <- matrix(seq(1, cols * ceiling(numPlots/cols)),
#                     ncol = cols, 
#                     nrow = ceiling(numPlots/cols))
#  }
#  if (numPlots == 1) {
#    print(plots[[1]])
#    } else {
#    # Set up the page
#    grid.newpage()
#    pushViewport(viewport(layout = grid.layout(nrow(layout), ncol(layout))))
#    # Make each plot, in the correct location
#    for (i in 1:numPlots) {
#      # Get the i,j matrix positions of the regions that contain this subplot
#      matchidx <- as.data.table(which(layout == i, arr.ind = TRUE))
#      print(plots[[i]], vp = viewport(layout.pos.row = matchidx[, row],
#                                      layout.pos.col = matchidx[, col]))
#    }
#  }
#}

Source Data Import

importing all companies’ list

file_in = paste('https://www.dropbox.com/s/', keys[AppName == 'felveteli_feladat_v2', 2], '?raw=1', sep = '')
company_list <- fread(file_in, 
                      sep = ';', 
                      na.strings = c('na', 'n/a', 'NA', 'N/A', ''), 
                      strip.white = TRUE, 
                      integer64 = 'numeric',
                      colClasses = list(character = 1, 5, 6, 7, 12, 14))
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0

100 97487    0 97487    0     0  62949      0 --:--:--  0:00:01 --:--:-- 62949
100 2222k    0 2222k    0     0   876k      0 --:--:--  0:00:02 --:--:-- 2151k
100 4334k    0 4334k    0     0  1225k      0 --:--:--  0:00:03 --:--:-- 2131k
100 5902k    0 5902k    0     0  1301k      0 --:--:--  0:00:04 --:--:-- 1943k
100 7662k    0 7662k    0     0  1383k      0 --:--:--  0:00:05 --:--:-- 1897k
100 9454k    0 9454k    0     0  1446k      0 --:--:--  0:00:06 --:--:-- 1876k
100 10.8M    0 10.8M    0     0  1478k      0 --:--:--  0:00:07 --:--:-- 1784k
100 12.4M    0 12.4M    0     0  1491k      0 --:--:--  0:00:08 --:--:-- 1679k
100 14.1M    0 14.1M    0     0  1518k      0 --:--:--  0:00:09 --:--:-- 1714k
100 15.9M    0 15.9M    0     0  1548k      0 --:--:--  0:00:10 --:--:-- 1731k
100 16.2M    0 16.2M    0     0  1552k      0 --:--:--  0:00:10 --:--:-- 1719k

Read 84.9% of 435704 rows
Read 435704 rows and 14 (of 14) columns from 0.051 GB file in 00:00:03

let’s bring column names into a general name convention

colnames(company_list) <- gsub(' ', '', stri_trans_totitle(gsub('_', ' ', stri_trans_general(colnames(company_list), 'Latin-ASCII'))))
company_list[, ':=' (FotevekenysegText = stri_trans_general(FotevekenysegText , 'Latin-ASCII'),
                     Szekhely = stri_trans_general(Szekhely, 'Latin-ASCII'),
                     SzekhelyVaros = stri_trans_general(SzekhelyVaros, 'Latin-ASCII'))]

saving

file_out = paste(getwd(), '/_tmp/company_list.RData', sep = '')
fwrite(company_list, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(company_list[sample(.N, 5)], split.table = 200)

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Adoszam   SajatToke   MerlegEredmeny   Arbevetel          Szekhely          SzekhelyVaros        FotevekenysegText        Letszam   Cegkora   AktivPrivatTulajdonos   AktivTulajdonosSzam   Cegforma 
--------- ----------- ---------------- ----------- ------------------------ --------------- ----------------------------- --------- --------- ----------------------- --------------------- ----------
11555535   81903000       10204000      84650000     4024 DEBRECEN VAR 10      DEBRECEN     Kereskedelem, gepjarmujavitas    NA        19                2                      2              KFT    

10690011   -3099000       -2012000      10085000   1147 BUDAPEST FURESZ 111    BUDAPEST           Adminisztrativ es           2        25                2                      2              KFT    
                                                                                               szolgaltatast tamogato                                                                                 
                                                                                                     tevekenyseg                                                                                      

24859705       0             0              0       1132 BUDAPEST VACI 64      BUDAPEST       Szallashely-szolgaltatas,      NA         3                1                      1              KFT    
                                                                                                     vendeglatas                                                                                      

20024389    1517000          0              0        7631 PECS MEGYERI 64        PECS       Szakmai, tudomanyos, muszaki     NA        25                2                      2               NA    
                                                                                                     tevekenyseg                                                                                      

23919662    4036000        340000       11401000    6300 KALOCSA ERDEI 54       KALOCSA           Ingatlanugyletek           NA         4                2                      2              KFT    
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
-----------------------------
 BankiUgyfel   AdoszamHosszu 
------------- ---------------
     NA        11555535-2-09 

     NA        10690011-2-42 

     NA        24859705-2-41 

     NA        20024389-2-02 

     NA        23919662-2-03 
-----------------------------

importing owner companies’ list

file_in = paste('https://www.dropbox.com/s/', keys[AppName == 'felveteli_feladat_tulajok_listaja', 2], '?raw=1', sep = '')
owner_company_list <- fread(file_in, 
                            sep = ';', 
                            na.strings = c('na', 'n/a', 'NA', 'N/A', ''), 
                            strip.white = TRUE, 
                            colClasses = list(character = 1, 2))
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0
  0     0    0     0    0     0      0      0 --:--:--  0:00:01 --:--:--     0

100 15550    0 15550    0     0   7642      0 --:--:--  0:00:02 --:--:--  7642
100  275k    0  275k    0     0   127k      0 --:--:--  0:00:02 --:--:-- 1959k

let’s bring column names into a general name convention

colnames(owner_company_list) <- gsub(' ', '', stri_trans_totitle(gsub('_', ' ', stri_trans_general(colnames(owner_company_list), 'Latin-ASCII'))))

saving

file_out = paste(getwd(), '/_tmp/owner_company_list.RData', sep = '')
fwrite(owner_company_list, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(owner_company_list[sample(.N, 5)], split.table = 200)

------------------------
 Adoszam   AdoszamTulaj 
--------- --------------
12287866     23277524   

23826551     23814323   

10752337     10458923   

25365375     11750170   

22979924     14083754   
------------------------

importing postcode list of bank branches

file_in = paste('https://www.dropbox.com/s/', keys[AppName == 'irszamlista', 2], '?raw=1', sep = '')
branch_zip_list <- fread(file_in,
                         sep = ';', 
                         na.strings = c('na', 'n/a', 'NA', 'N/A', ''), 
                         strip.white = TRUE,
                         colClasses = list(character = 1))
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0

100   203    0   203    0     0    246      0 --:--:-- --:--:-- --:--:--   246
100   203    0   203    0     0    246      0 --:--:-- --:--:-- --:--:--     0
setnames(branch_zip_list, 'irányítószám', 'IrSzam')

saving

file_out = paste(getwd(), '/_tmp/branch_zip_list.RData', sep = '')
fwrite(branch_zip_list, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(branch_zip_list[sample(.N, 5)], split.table = 200)

--------
 IrSzam 
--------
  6065  

  6334  

  6133  

  6237  

  6413  
--------

Data Exploration & Cleaning

initial summary of company list - let’s check a sample of the data

pander(summary(company_list), split.table = 200)

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
    Adoszam          SajatToke        MerlegEredmeny       Arbevetel          Szekhely      SzekhelyVaros    FotevekenysegText      Letszam        Cegkora      AktivPrivatTulajdonos 
---------------- ------------------ ------------------ ------------------ ---------------- ---------------- ------------------- --------------- -------------- -----------------------
 Length:435704   Min.  :-3.787e+10  Min.  :-6.802e+10  Min.  :-8.941e+08   Length:435704    Length:435704      Length:435704     Min.  : 1.00    Min.  : 0.00       Min.  : 0.000     

Class :character 1st Qu.: 0.000e+00 1st Qu.:-7.200e+04 1st Qu.: 9.000e+03 Class :character Class :character  Class :character    1st Qu.: 1.00  1st Qu.: 5.00      1st Qu.: 1.000     

Mode :character  Median : 2.861e+06 Median : 2.600e+04 Median : 5.525e+06 Mode :character  Mode :character    Mode :character    Median : 2.00  Median : 9.00      Median : 2.000     

       NA         Mean : 9.321e+07   Mean : 1.042e+07   Mean : 1.681e+08         NA               NA                NA           Mean : 14.08    Mean : 11.26       Mean : 2.014      

       NA        3rd Qu.: 1.051e+07 3rd Qu.: 1.120e+06 3rd Qu.: 2.921e+07        NA               NA                NA           3rd Qu.: 4.00  3rd Qu.: 17.00     3rd Qu.: 2.000     

       NA        Max.  : 3.498e+12  Max.  : 2.866e+12  Max.  : 4.396e+12         NA               NA                NA          Max.  :31646.00 Max.  :116.00      Max.  :908.000     

       NA                NA                 NA                 NA                NA               NA                NA           NA's :313007     NA's :384          NA's :16318      
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
---------------------------------------------------------------------
 AktivTulajdonosSzam      Cegforma      BankiUgyfel   AdoszamHosszu  
--------------------- ---------------- ------------- ----------------
    Min.  : 1.000      Length:435704     Min.  :1     Length:435704  

   1st Qu.: 1.000     Class :character   1st Qu.:1   Class :character

   Median : 2.000     Mode :character    Median :1   Mode :character 

    Mean : 2.125             NA           Mean :1           NA       

   3rd Qu.: 2.000            NA          3rd Qu.:1          NA       

   Max.  :914.000            NA          Max.  :1           NA       

     NA's :16318             NA        NA's :430619         NA       
---------------------------------------------------------------------

prerequisite of detection of Adoszam values being unique is to order them by that column

company_list <- company_list[order(Adoszam)]

let’s check column values validity

ct <- check_that(company_list, 
                 valid_notation_BankiUgyfel = BankiUgyfel == 0
                                            | BankiUgyfel == 1,
                 distinct_Adoszam = rle(sort(Adoszam))[[1]] == 1,
                 complete_Adoszam = stri_length(Adoszam) == 8,
                 complete_AdoszamHosszu = stri_length(AdoszamHosszu) == 13,
                 identical_Adoszam_and_AdoszamHosszu = Adoszam == substr(AdoszamHosszu,1 , 8),
                 initcap_Cegforma = Cegforma == stri_trans_totitle(Cegforma),
                 valid_AktivTulajdonosSzam = AktivTulajdonosSzam >= 1,
                 valid_AktivPrivatTulajdonos = AktivTulajdonosSzam >= AktivPrivatTulajdonos,
                 filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos = stri_length(AktivTulajdonosSzam) >= 1
                                                                      & stri_length(AktivPrivatTulajdonos) >= 1,
                 not_negative_Cegkora = Cegkora >= 0,
                 under_UCL_Cegkora = Cegkora <= mean(Cegkora, na.rm = TRUE) + 3 * sd(Cegkora, na.rm = TRUE),
                 valid_Letszam = Letszam >= 1,
                 filled_FotevekenysegText = stri_length(FotevekenysegText) >= 1,
                 initcap_FotevekenysegText = FotevekenysegText == stri_trans_totitle(FotevekenysegText),
                 valid_postcode = nchar(sub(' .*', '', stri_trim(Szekhely))) == 4,
                 valid_SzekhelyVaros = stri_length(SzekhelyVaros) >= 2
                                     & grepl('\\d', SzekhelyVaros) == FALSE,
                 initcap_SzekhelyVaros = SzekhelyVaros == stri_trans_totitle(SzekhelyVaros), 
                 valid_Szekhely = stri_length(Szekhely) >= 11
                                & grepl('\\D', substr(stri_trim(Szekhely), 1, 4)) == FALSE
                                & grepl('\\^', Szekhely) == FALSE,
                 initcap_Szekhely = Szekhely == stri_trans_totitle(Szekhely), 
                 filled_Arbevetel = stri_length(Arbevetel) >= 1,
                 filled_MerlegEredmeny = stri_length(MerlegEredmeny) >= 1,
                 filled_SajatToke = stri_length(SajatToke) >= 1,
                 filled_FotevekenysegText_and_AktivTulajdonosSzam_and_Cegforma = stri_length(FotevekenysegText) >= 1
                                                                               & stri_length(AktivTulajdonosSzam) >= 1
                                                                               & stri_length(Cegforma) >= 1
                 )
dt <- data.table(summary(ct))
dt <- dt[, c(1:5, 8)]

let’s see the validity of the data

pander(dt, split.table = 200)

-----------------------------------------------------------------------------------------------------------------------------------
                            rule                               items   passes   fails   nNA                expression              
------------------------------------------------------------- ------- -------- ------- ------ -------------------------------------
                 valid_notation_BankiUgyfel                   435704    5085      0    430619    abs(BankiUgyfel - 0) < 1e-08 |    
                                                                                                  abs(BankiUgyfel - 1) < 1e-08     

                      distinct_Adoszam                        435439   435229    210     0        rle(sort(Adoszam))[[1]] == 1     

                      complete_Adoszam                        435704   435704     0      0          stri_length(Adoszam) == 8      

                   complete_AdoszamHosszu                     435704   435219    24     461       stri_length(AdoszamHosszu) ==    
                                                                                                               13                  

             identical_Adoszam_and_AdoszamHosszu              435704   435112    131    461                Adoszam ==              
                                                                                                   substr(AdoszamHosszu, 1, 8)     

                      initcap_Cegforma                        435704     0     400465  35239               Cegforma ==             
                                                                                                  stri_trans_totitle(Cegforma)     

                  valid_AktivTulajdonosSzam                   435704   419386     0    16318        AktivTulajdonosSzam >= 1       

                 valid_AktivPrivatTulajdonos                  435704   419386     0    16318         AktivTulajdonosSzam >=        
                                                                                                      AktivPrivatTulajdonos        

    filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos      435704   419386     0    16318    stri_length(AktivTulajdonosSzam)   
                                                                                                             >= 1 &                
                                                                                               stri_length(AktivPrivatTulajdonos)  
                                                                                                              >= 1                 

                    not_negative_Cegkora                      435704   435320     0     384               Cegkora >= 0             

                      under_UCL_Cegkora                       435704   434401    919    384      Cegkora <= mean(Cegkora, na.rm    
                                                                                                    = TRUE) + 3 * sd(Cegkora,      
                                                                                                          na.rm = TRUE)            

                        valid_Letszam                         435704   122697     0    313007             Letszam >= 1             

                  filled_FotevekenysegText                    435704   370787     0    64917     stri_length(FotevekenysegText)    
                                                                                                              >= 1                 

                  initcap_FotevekenysegText                   435704   99980   270807  64917          FotevekenysegText ==         
                                                                                              stri_trans_totitle(FotevekenysegText)

                       valid_postcode                         435704   435694     9      1            nchar(sub(" .*", "",         
                                                                                                   stri_trim(Szekhely))) == 4      

                     valid_SzekhelyVaros                      435704   435700     3      1        stri_length(SzekhelyVaros) >=    
                                                                                                        2 & grepl("\\d",           
                                                                                                     SzekhelyVaros) == FALSE       

                    initcap_SzekhelyVaros                     435704     7     435696    1              SzekhelyVaros ==           
                                                                                                stri_trans_totitle(SzekhelyVaros)  

                       valid_Szekhely                         435704   435691    12      1        stri_length(Szekhely) >= 11 &    
                                                                                                          grepl("\\D",             
                                                                                                 substr(stri_trim(Szekhely), 1,    
                                                                                                   4)) == FALSE & grepl("\\^",     
                                                                                                       Szekhely) == FALSE          

                      initcap_Szekhely                        435704     5     435698    1                 Szekhely ==             
                                                                                                  stri_trans_totitle(Szekhely)     

                      filled_Arbevetel                        435704   435704     0      0         stri_length(Arbevetel) >= 1     

                    filled_MerlegEredmeny                     435704   435704     0      0       stri_length(MerlegEredmeny) >=    
                                                                                                                1                  

                      filled_SajatToke                        435704   435704     0      0         stri_length(SajatToke) >= 1     

filled_FotevekenysegText_and_AktivTulajdonosSzam_and_Cegforma 435704   328402     0    107302    stri_length(FotevekenysegText)    
                                                                                                             >= 1 &                
                                                                                                stri_length(AktivTulajdonosSzam)   
                                                                                                  >= 1 & stri_length(Cegforma)     
                                                                                                              >= 1                 
-----------------------------------------------------------------------------------------------------------------------------------

Adoszam is not distinct in the table, so I drop the repeating lines and double check it

company_list <- data.table(unique(company_list))

prerequisite of detection of Adoszam values being unique is to order them by that column

company_list <- company_list[order(Adoszam)]
ct <- check_that(company_list,
                 distinct_Adoszam = rle(sort(Adoszam))[[1]] == 1)
dt <- data.table(summary(ct))
dt <- dt[, c(1:5, 8)]

let’s see the validity of the data

pander(dt, split.table = 200)

----------------------------------------------------------------------------
      rule        items   passes   fails   nNA           expression         
---------------- ------- -------- ------- ----- ----------------------------
distinct_Adoszam 435439   435439     0      0   rle(sort(Adoszam))[[1]] == 1
----------------------------------------------------------------------------

It seems ok. We got rid of duplicates.

let’s drop commas from Szekhely and SzekhelyVaros columns & check a sample of the data

pander(company_list[grepl(',', Szekhely) | grepl(',', SzekhelyVaros), .(Adoszam, Szekhely,  SzekhelyVaros)])

------------------------------------------------------------
 Adoszam             Szekhely               SzekhelyVaros   
--------- ------------------------------ -------------------
10118720   0, Budapest, V., Aranykez u.        dapest,      
                        2.                                  

10223783  0, Budapest, XII.ker. Diosarok       dapest,      
                      ut 62.                                

10386543    114, Budapest, Villanyi ut        Budapest,     
                      11-13.                                

11541082    0, Pecs, Szarvas dulo 15.          �cs,       

12291625   0, Sajoszentpeter, Jozsef A.     joszentpeter,   
                      u. 42.                                

20009946        0, Pecs, Fellbach              �cs,       
                 Bevasarlokozpont                           

22810773     0, Debrecen-Erdospuszta,    brecen-Erdospuszta,
                  Vekeri-piheno                             

23660258   0, Tatabanya, II., Sarberki        tabanya,      
                    uzeletsor                               

29628203   0, Nagykoros, Barany u. 5/b.       gykoros,      

29814730  0, Budapest, VII.ker. Izabella       dapest,      
                    u. 36-38.                               
------------------------------------------------------------
company_list[grepl(',', Szekhely), Szekhely := gsub(',', '', Szekhely)]
company_list[grepl(',', SzekhelyVaros), SzekhelyVaros := gsub(',', '', SzekhelyVaros)]

let’s transform text columns to initial capitals

company_list[Cegforma != stri_trans_totitle(Cegforma), Cegforma := stri_trans_totitle(Cegforma)]
company_list[FotevekenysegText != stri_trans_totitle(FotevekenysegText), FotevekenysegText := stri_trans_totitle(FotevekenysegText)]
company_list[SzekhelyVaros != stri_trans_totitle(SzekhelyVaros), SzekhelyVaros := stri_trans_totitle(SzekhelyVaros)]
company_list[Szekhely != stri_trans_totitle(Szekhely), Szekhely := stri_trans_totitle(Szekhely)]

let’s transform the dependent variable (BankiUgyfel) into a real dummy variable

company_list[BankiUgyfel == 1, BankiUgyfel_ := TRUE]
company_list[is.na(BankiUgyfel), BankiUgyfel_ := FALSE]
company_list[, BankiUgyfel := NULL]
setnames(company_list, 'BankiUgyfel_', 'BankiUgyfel')

let’s check a sample of the data

pander(company_list[, .N, by = BankiUgyfel], split.table = 200)

--------------------
 BankiUgyfel    N   
------------- ------
    FALSE     430619

    TRUE       4820 
--------------------

there are some companies with not matching Adoszam and AdoszamHosszu / I drop those AdoszamHosszu data

n <- company_list[Adoszam != substr(AdoszamHosszu,1 , 8), .N]
pander(paste('The number of not matching AdoszamHosszu data: ', n, sep = ''))
The number of not matching AdoszamHosszu data: 131

let’s check a sample of the data

pander(company_list[Adoszam != substr(AdoszamHosszu,1 , 8), .(Adoszam, AdoszamHosszu, Szekhely)][sample(.N, 5)], split.table = 200)

---------------------------------------------------
 Adoszam   AdoszamHosszu          Szekhely         
--------- --------------- -------------------------
10959493   10785052-1-43   1111 Budapest Bartok 36 

24465207   24460185-2-41  1011 Budapest Jegverem 8 

10936272   10930272-1-43   1118 Budapest Menesi 36 

12180075   12180675-2-41  1037 Budapest Fergeteg 13

28274193   28103972-2-42    1011 Budapest Maria 3  
---------------------------------------------------

dropping not matching AdoszamHosszu values

company_list[Adoszam != substr(AdoszamHosszu,1 , 8), AdoszamHosszu := NA]

there are some companies with not complete AdoszamHosszu / I drop those AdoszamHosszu values

n <- company_list[stri_length(AdoszamHosszu) < 13, .N]
pander(paste('The number of not complete AdoszamHosszu data: ', n, sep = ''))
The number of not complete AdoszamHosszu data: 19

let’s check a sample of the data

pander(company_list[stri_length(AdoszamHosszu) < 13, .(Adoszam, AdoszamHosszu, Szekhely)][sample(.N, 5)], split.table = 200)

---------------------------------------------------
 Adoszam   AdoszamHosszu          Szekhely         
--------- --------------- -------------------------
10894576   10894576-0-1    2367 Ujhartyan Malom 22 

23619812   23619812-1-1    2521 Csolnok Szedres 5  

11221492   11221492-1-4    8700 Marcali Kossuth 74 

20006802   20006802-0-2   7900 Szigetvar Fertokoz 8

11430384   11430384-0-4   5900 Oroshaza Csorvasi 26
---------------------------------------------------

dropping nonsense AdoszamHosszu values

company_list[stri_length(AdoszamHosszu) < 13, AdoszamHosszu := NA]

there are some probable outliers in terms of CegKora / I have double checked, there could be companies with this age out there

dt <- company_list[Cegkora > mean(Cegkora, na.rm = TRUE) + 3 * sd(Cegkora, na.rm = TRUE), .N, by = Cegkora]
dt[order(-Cegkora)]

there are some FotevekenysegText values are missing / I will fill them up with as NemIsmert & collapse value strings

n <- company_list[is.na(FotevekenysegText), .N]
pander(paste('The number of missing FotevekenysegText values: ', n, sep = ''))
The number of missing FotevekenysegText values: 64885
company_list[is.na(FotevekenysegText), FotevekenysegText := 'NemIsmert']
company_list[grepl(',', FotevekenysegText), FotevekenysegText := gsub(',', '', FotevekenysegText)]
company_list[grepl(' ', FotevekenysegText), FotevekenysegText := gsub(' ', '', FotevekenysegText)]
dt <- data.table(company_list[, .N, by = FotevekenysegText])

let’s check a sample of the data

pander(dt[order(-N, FotevekenysegText)], split.table = 200)

------------------------------------------------------
               FotevekenysegText                   N  
------------------------------------------------ -----
          KereskedelemGepjarmujavitas            88193

                   NemIsmert                     64885

      SzakmaiTudomanyosMuszakiTevekenyseg        58536

                   Epitoipar                     32956

                 Feldolgozoipar                  31117

                Ingatlanugyletek                 28767

             InformacioKommunikacio              20972

AdminisztrativEsSzolgaltatastTamogatoTevekenyseg 20655

      Szallashely-SzolgaltatasVendeglatas        18166

       Human-EgeszsegugyiSzocialisEllatas        13079

              SzallitasRaktarozas                12977

      MezogazdasagErdogazdalkodasHalaszat        10592

         PenzugyiBiztositasiTevekenyseg          9558 

               EgyebSzolgaltatas                 7478 

         MuveszetSzorakoztatasSzabadido          7322 

                    Oktatas                      7081 

       VizellatasSzennyviz-Hulladekgazd.         1558 

 VillamosenergiaGaz-HozellatasLegkondicionalas    897 

               BanyaszatKofejtes                  429 

KozigazgatasVedelemKotelezoTarsadalombiztositas   207 

         HaztartasMunkaadoiTevekenysege           13  

            TeruletenKivuliSzervezet               1  
------------------------------------------------------

there are some not valid Szekhely values / I drop those Szekhely values

n <- company_list[stri_length(Szekhely) < 11
                            | grepl('\\D', substr(stri_trim(Szekhely), 1, 4))
                            | grepl('\\^', Szekhely),
                            .N]
pander(paste('The number of not valid Szekhely values: ', n, sep = ''))
The number of not valid Szekhely values: 12

let’s check a sample of the data

pander(company_list[stri_length(Szekhely) < 11
                  | grepl('\\D', substr(stri_trim(Szekhely), 1, 4))
                  | grepl('\\^', Szekhely)
                  , .(Adoszam, Szekhely, SzekhelyVaros)], split.table = 200)

----------------------------------------------------------
 Adoszam            Szekhely              SzekhelyVaros   
--------- ----------------------------- ------------------
10118720  0 Budapest V. Aranykez U. 2.        Dapest      

10223783  0 Budapest Xii.ker. Diosarok        Dapest      
                     Ut 62.                               

10386543    114 Budapest Villanyi Ut         Budapest     
                     11-13.                               

11541082     0 Pecs Szarvas Dulo 15.          �Cs       

12291625  0 Sajoszentpeter Jozsef A. U.    Joszentpeter   
                       42.                                

14709070           7630 P E 5                   P         

14792335           6500 B A 3                   B         

20009946         0 Pecs Fellbach              �Cs       
                Bevasarlokozpont                          

22810773     0 Debrecen-Erdospuszta     Brecen-Erdospuszta
                  Vekeri-Piheno                           

23660258    0 Tatabanya Ii. Sarberki         Tabanya      
                    Uzeletsor                             

29628203   0 Nagykoros Barany U. 5/B.        Gykoros      

29814730  0 Budapest Vii.ker. Izabella        Dapest      
                    U. 36-38.                             
----------------------------------------------------------

dropping nonsense Szekhely values

company_list[stri_length(Szekhely) < 11
            | grepl('\\D', substr(stri_trim(Szekhely), 1, 4))
            | grepl('\\^', Szekhely)             
            , Szekhely := NA]

there are some not valid SzekhelyVaros values / I drop those SzekhelyVaros values

n <- company_list[stri_length(stri_enc_toutf8(SzekhelyVaros, validate = TRUE)) < 2
                            | grepl('\\d', stri_enc_toutf8(SzekhelyVaros, validate = TRUE)), .N]
pander(paste('The number of not valid SzekhelyVaros values: ', n, sep = ''))
The number of not valid SzekhelyVaros values: 3

let’s check a sample of the data

pander(company_list[stri_length(stri_enc_toutf8(SzekhelyVaros, validate = TRUE)) < 2
                   | grepl('\\d', stri_enc_toutf8(SzekhelyVaros, validate = TRUE)) 
                   , .(Adoszam, Szekhely, stri_enc_toutf8(SzekhelyVaros, validate = TRUE))], split.table = 200)

---------------------------------------------
 Adoszam          Szekhely             V3    
--------- ------------------------- ---------
10932807   1047 Budapest6 Foti 56   Budapest6

12769621  2009 0201Pest Bocskai 104 0201Pest 

23817797  7960 7960 Dravasztara 25    7960   
---------------------------------------------

dropping nonsense SzekhelyVaros values

company_list[stri_length(stri_enc_toutf8(SzekhelyVaros, validate = TRUE)) < 2
           | grepl('\\d', stri_enc_toutf8(SzekhelyVaros, validate = TRUE)) 
           , SzekhelyVaros := NA]

saving modified company_list in RData format

file_out = paste(getwd(), '/_tmp/company_list.RData', sep = '')
fwrite(company_list, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(company_list[sample(.N, 5)], split.table = 200)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Adoszam   SajatToke   MerlegEredmeny   Arbevetel            Szekhely               SzekhelyVaros                   FotevekenysegText                  Letszam   Cegkora   AktivPrivatTulajdonos 
--------- ----------- ---------------- ----------- ----------------------------- ------------------- ------------------------------------------------ --------- --------- -----------------------
21503041     56000           0              0          2146 Mogyorod Szolo 6          Mogyorod                      EgyebSzolgaltatas                    NA        14                2           

12536036    5521000       2142000        8650000   8900 Zalaegerszeg Jegmadar 16    Zalaegerszeg     AdminisztrativEsSzolgaltatastTamogatoTevekenyseg    NA        16                1           

14221477    -390000        103000        672000      2509 Esztergom-Kertvaros    Esztergom-Kertvaros                    NemIsmert                        NA         9                2           
                                                           Wesselenyi 11                                                                                                                         

24110361   -8234000        635000       36583000        3070 Batonyterenye          Batonyterenye                       Epitoipar                         4         4               NA           
                                                          Szorospataki 31                                                                                                                        

10696718   14593000       3101000       15512000    4400 Nyiregyhaza Szegfu 48       Nyiregyhaza     AdminisztrativEsSzolgaltatastTamogatoTevekenyseg     1        25                2           
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
--------------------------------------------------------------
 AktivTulajdonosSzam   Cegforma   AdoszamHosszu   BankiUgyfel 
--------------------- ---------- --------------- -------------
          2               Bt      21503041-1-13      FALSE    

          1              Kft      12536036-2-20      FALSE    

          2              Kft      14221477-2-11      FALSE    

         NA              Kft      24110361-2-12      FALSE    

          2              Kft      10696718-2-15      FALSE    
--------------------------------------------------------------

Let’s take a look at histograms of columns (variables) of company list

p1 <- ggplot(company_list) + 
        geom_histogram(aes(x = SajatToke),
                       fill = 'blue', 
                       position = 'identity',
                       binwidth = 0.3,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_SajatToke') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_SajatToke') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)  
p2 <- ggplot(company_list) + 
        geom_histogram(aes(x = MerlegEredmeny),
                       fill = 'green', 
                       position = 'identity',
                       binwidth = 0.3,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_MerlegEredmeny') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_MerlegEredmeny') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)  
p3 <- ggplot(company_list) + 
        geom_histogram(aes(x = Arbevetel),
                       fill = 'orange', 
                       position = 'identity',
                       binwidth = 0.3,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_Arbevetel') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_Arbevetel') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)
p4 <- ggplot(company_list) + 
        geom_histogram(aes(x = AktivPrivatTulajdonos),
                       fill = 'red', 
                       position = 'identity',
                       binwidth = 0.1,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_AktivPrivatTulajdonos') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_AktivPrivatTulajdonos') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)
p5 <- ggplot(company_list) + 
        geom_histogram(aes(x = AktivTulajdonosSzam),
                       fill = 'lightgreen', 
                       position = 'identity',
                       binwidth = 0.1,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_AktivTulajdonosSzam') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_AktivTulajdonosSzam') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)
p6 <- ggplot(company_list) + 
        geom_histogram(aes(x = Cegkora),
                       fill = 'brown',
                       position = 'identity',
                       binwidth = 0.2,
                       alpha = I(0.7)) +
        ggtitle('Histogram for log10_Cegkora') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_Cegkora') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)
p7 <- ggplot(company_list) + 
        geom_histogram(aes(x = Letszam),
                       fill = 'orange',
                       position = 'identity',
                       binwidth = 0.3,
                       alpha = I(0.7)) +
        ggtitle('Histogram for log10_Letszam') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_Letszam') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)
p8 <- ggplot(company_list) +
        geom_bar(aes(x = as.factor(Cegforma)),
                 fill = 'blue',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of Cegforma') +
        xlab('Cegforma') +
        theme_igray()
Sys.sleep(2)
p9 <- ggplot(company_list) +
        geom_bar(aes(x = as.factor(FotevekenysegText)),
                 fill = 'darkgreen',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of FotevekenysegText') +
        xlab('FotevekenysegText') +
        theme_igray()
Sys.sleep(2)
p10 <- ggplot(company_list) +
        geom_bar(aes(x = as.factor(BankiUgyfel)),
                 fill = 'lightblue',
                 position = 'identity',
                 alpha = I(0.7)) +
        ggtitle('Breakdown of BankiUgyfel') +
        xlab('BankiUgyfel') +
        theme_igray()
Sys.sleep(2)

without plotly in one grid:

# multiplot(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, cols = 2)

transforming into interactive graph

pp1 <- ggplotly(p1)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp1.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp1), file_out)

transforming into interactive graph

pp2 <- ggplotly(p2)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp2.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp2), file_out)

transforming into interactive graph

pp3 <- ggplotly(p3)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp3.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp3), file_out)

transforming into interactive graph

pp4 <- ggplotly(p4)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp4.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp4), file_out)

transforming into interactive graph

pp5 <- ggplotly(p5)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp5.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp5), file_out)

transforming into interactive graph

pp6 <- ggplotly(p6)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp6.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp6), file_out)

transforming into interactive graph

pp7 <- ggplotly(p7)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp7.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp7), file_out)

transforming into interactive graph

pp8 <- ggplotly(p8)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp8.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp8), file_out)

transforming into interactive graph

pp9 <- ggplotly(p9)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp9.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp9), file_out)

transforming into interactive graph

pp10 <- ggplotly(p10)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp10.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp10), file_out)

plotting

layout(pp1, dragmode = 'pan')

Sys.sleep(2)
layout(pp2, dragmode = 'pan')

Sys.sleep(2)
layout(pp3, dragmode = 'pan')

Sys.sleep(2)
layout(pp4, dragmode = 'pan')

Sys.sleep(2)
layout(pp5, dragmode = 'pan')

Sys.sleep(2)
layout(pp6, dragmode = 'pan')

Sys.sleep(2)
layout(pp7, dragmode = 'pan')

Sys.sleep(2)
layout(pp8, dragmode = 'pan')

Sys.sleep(2)
layout(pp9, dragmode = 'pan')

Sys.sleep(2)
layout(pp10, dragmode = 'pan')

Sys.sleep(2)

deallocating memory!

rm(company_list, p1, p2, p3, p4, p5, p6, p7, p8, p9, p10)

Preparation steps before Enrichment

downloading & decompressing postcode list of all Hungarian settlements into a corresponding folder

temp <- tempfile()
download.file('http://download.geonames.org/export/zip/HU.zip', temp)
trying URL 'http://download.geonames.org/export/zip/HU.zip'
Content type 'application/zip' length 45266 bytes (44 KB)
==================================================
downloaded 44 KB
unzip(zipfile = temp, exdir = paste(getwd(), '/_tmp/', sep = ''))
file.rename(paste(getwd(), '/_tmp/HU.txt', sep = ''), paste(getwd(), '/_tmp/postcode_list.csv', sep = ''))
[1] TRUE
file.rename(paste(getwd(), '/_tmp/readme.txt', sep = ''), paste(getwd(), '/_tmp/postcode_list_readme.txt', sep = ''))
[1] TRUE

importing into R environment

postcode_list <- data.table(fread(paste(getwd(), '/_tmp/postcode_list.csv', sep = '')))
postcode_list[, ':=' (IrSzam = as.character(V2), Megye = as.character(V4), Varos = as.character(V3), Lat = V10, Lon = V11)]
postcode_list <- unique(postcode_list[, c(13:17)])

changing accented characters into non-accented ones in the downloaded data

postcode_list[, ':=' (Megye = stri_trans_general(Megye, 'Latin-ASCII'), Varos = stri_trans_general(Varos, 'Latin-ASCII'))]

let’s put together those cities which shares the same postcode and average out geocodes on postcode level postcodes shared among at least 2 cities will only have one (averaged) geocode so different settlements will have the same geocodes

postcode_list <- data.table(sqldf('SELECT  IrSzam
                                          ,GROUP_CONCAT(DISTINCT Megye) AS Megye 
                                          ,GROUP_CONCAT(DISTINCT Varos) AS Varos
                                          ,AVG(Lat) AS Lat
                                          ,AVG(Lon) AS Lon
                                    FROM  postcode_list
                                    GROUP BY IrSzam'))
Loading required package: tcltk

Let’s check with eyeballing whether we have all of the cities or not (it seems, we have)

dt <- copy(data.table(postcode_list[, .N, by = .(Lat, Lon, Megye)]))
Hungary <- get_map(location = c(16, 45.7, 23, 48.6), 
                    zoom = 8, 
                    source = 'stamen', 
                    maptype = 'toner-lite', 
                    messaging = TRUE, 
                    language = 'hu-HU',
                    filename = paste(getwd(), '/_tmp/HungaryTemp', sep = ''))
24 tiles required.
trying URL 'http://tile.stamen.com/toner-lite/8/139/88.png'
Content type 'image/png' length 19154 bytes (18 KB)
==================================================
downloaded 18 KB

Map from URL : http://tile.stamen.com/toner-lite/8/139/88.png
trying URL 'http://tile.stamen.com/toner-lite/8/140/88.png'
Content type 'image/png' length 22297 bytes (21 KB)
==================================================
downloaded 21 KB

Map from URL : http://tile.stamen.com/toner-lite/8/140/88.png
trying URL 'http://tile.stamen.com/toner-lite/8/141/88.png'
Content type 'image/png' length 15163 bytes (14 KB)
==================================================
downloaded 14 KB

Map from URL : http://tile.stamen.com/toner-lite/8/141/88.png
trying URL 'http://tile.stamen.com/toner-lite/8/142/88.png'
Content type 'image/png' length 13851 bytes (13 KB)
==================================================
downloaded 13 KB

Map from URL : http://tile.stamen.com/toner-lite/8/142/88.png
trying URL 'http://tile.stamen.com/toner-lite/8/143/88.png'
Content type 'image/png' length 16986 bytes (16 KB)
==================================================
downloaded 16 KB

Map from URL : http://tile.stamen.com/toner-lite/8/143/88.png
trying URL 'http://tile.stamen.com/toner-lite/8/144/88.png'
Content type 'image/png' length 10574 bytes (10 KB)
==================================================
downloaded 10 KB

Map from URL : http://tile.stamen.com/toner-lite/8/144/88.png
trying URL 'http://tile.stamen.com/toner-lite/8/139/89.png'
Content type 'image/png' length 14527 bytes (14 KB)
==================================================
downloaded 14 KB

Map from URL : http://tile.stamen.com/toner-lite/8/139/89.png
trying URL 'http://tile.stamen.com/toner-lite/8/140/89.png'
Content type 'image/png' length 17520 bytes (17 KB)
==================================================
downloaded 17 KB

Map from URL : http://tile.stamen.com/toner-lite/8/140/89.png
trying URL 'http://tile.stamen.com/toner-lite/8/141/89.png'
Content type 'image/png' length 23927 bytes (23 KB)
==================================================
downloaded 23 KB

Map from URL : http://tile.stamen.com/toner-lite/8/141/89.png
trying URL 'http://tile.stamen.com/toner-lite/8/142/89.png'
Content type 'image/png' length 18922 bytes (18 KB)
==================================================
downloaded 18 KB

Map from URL : http://tile.stamen.com/toner-lite/8/142/89.png
trying URL 'http://tile.stamen.com/toner-lite/8/143/89.png'
Content type 'image/png' length 18461 bytes (18 KB)
==================================================
downloaded 18 KB

Map from URL : http://tile.stamen.com/toner-lite/8/143/89.png
trying URL 'http://tile.stamen.com/toner-lite/8/144/89.png'
Content type 'image/png' length 12786 bytes (12 KB)
==================================================
downloaded 12 KB

Map from URL : http://tile.stamen.com/toner-lite/8/144/89.png
trying URL 'http://tile.stamen.com/toner-lite/8/139/90.png'
Content type 'image/png' length 13670 bytes (13 KB)
==================================================
downloaded 13 KB

Map from URL : http://tile.stamen.com/toner-lite/8/139/90.png
trying URL 'http://tile.stamen.com/toner-lite/8/140/90.png'
Content type 'image/png' length 15049 bytes (14 KB)
==================================================
downloaded 14 KB

Map from URL : http://tile.stamen.com/toner-lite/8/140/90.png
trying URL 'http://tile.stamen.com/toner-lite/8/141/90.png'
Content type 'image/png' length 15409 bytes (15 KB)
==================================================
downloaded 15 KB

Map from URL : http://tile.stamen.com/toner-lite/8/141/90.png
trying URL 'http://tile.stamen.com/toner-lite/8/142/90.png'
Content type 'image/png' length 20012 bytes (19 KB)
==================================================
downloaded 19 KB

Map from URL : http://tile.stamen.com/toner-lite/8/142/90.png
trying URL 'http://tile.stamen.com/toner-lite/8/143/90.png'
Content type 'image/png' length 12477 bytes (12 KB)
==================================================
downloaded 12 KB

Map from URL : http://tile.stamen.com/toner-lite/8/143/90.png
trying URL 'http://tile.stamen.com/toner-lite/8/144/90.png'
Content type 'image/png' length 10303 bytes (10 KB)
==================================================
downloaded 10 KB

Map from URL : http://tile.stamen.com/toner-lite/8/144/90.png
trying URL 'http://tile.stamen.com/toner-lite/8/139/91.png'
Content type 'image/png' length 15069 bytes (14 KB)
==================================================
downloaded 14 KB

Map from URL : http://tile.stamen.com/toner-lite/8/139/91.png
trying URL 'http://tile.stamen.com/toner-lite/8/140/91.png'
Content type 'image/png' length 11172 bytes (10 KB)
==================================================
downloaded 10 KB

Map from URL : http://tile.stamen.com/toner-lite/8/140/91.png
trying URL 'http://tile.stamen.com/toner-lite/8/141/91.png'
Content type 'image/png' length 16781 bytes (16 KB)
==================================================
downloaded 16 KB

Map from URL : http://tile.stamen.com/toner-lite/8/141/91.png
trying URL 'http://tile.stamen.com/toner-lite/8/142/91.png'
Content type 'image/png' length 14413 bytes (14 KB)
==================================================
downloaded 14 KB

Map from URL : http://tile.stamen.com/toner-lite/8/142/91.png
trying URL 'http://tile.stamen.com/toner-lite/8/143/91.png'
Content type 'image/png' length 12728 bytes (12 KB)
==================================================
downloaded 12 KB

Map from URL : http://tile.stamen.com/toner-lite/8/143/91.png
trying URL 'http://tile.stamen.com/toner-lite/8/144/91.png'
Content type 'image/png' length 14286 bytes (13 KB)
==================================================
downloaded 13 KB

Map from URL : http://tile.stamen.com/toner-lite/8/144/91.png

saving

file_out = paste(getwd(), '/_tmp/Hungary.rda', sep = '')
save(Hungary, file = file_out)
  ggmap(Hungary) +
  geom_point(data = dt,
             aes(x = Lon,
                 y = Lat,
                 color = Megye,
                 size = N),
             alpha = 0.7,
             stroke = 1.2,
             shape = 1) +
  ggtitle('Number of Postcodes per Cities on Hungary')

let’s check a sample of the data

pander(postcode_list[sample(.N, 5)], split.table = 200)

---------------------------------------------
 IrSzam     Megye       Varos      Lat   Lon 
-------- ----------- ------------ ----- -----
  3324      Heves    Felsotarkany 47.97 20.42

  8821      Zala     Nagybakonak  46.55 17.05

  6717    Csongrad      Szeged    46.32 20.03

  6334   Bacs-Kiskun   Gederlak   46.62 18.92

  8081      Fejer       Zamoly    47.32 18.42
---------------------------------------------

deallocating memory!

rm(temp)

downloading those companies list which employed people without registration from http://nav.gov.hu/nav/adatbazisok/benemjelentett this link name (and the content also) is changing weekly, so I used Selenium

Page <- 'http://nav.gov.hu/nav/adatbazisok/benemjelentett'
XpathValue <- '//*[@id="portal"]/div[6]/div[2]/div[2]/div[2]/div/div/div/table/tbody/tr[2]/td[2]/a'
rD <- rsDriver(port = 4567L, 
               browser = 'chrome', 
               version = 'latest', 
               chromever = 'latest',
               geckover = 'latest', 
               phantomver = '2.1.1',
               verbose = TRUE, 
               check = TRUE)
checking Selenium Server versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
BEGIN: POSTDOWNLOAD
checking chromedriver versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
BEGIN: POSTDOWNLOAD
checking geckodriver versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
BEGIN: POSTDOWNLOAD
checking phantomjs versions:
BEGIN: PREDOWNLOAD
BEGIN: DOWNLOAD
BEGIN: POSTDOWNLOAD
[1] "Connecting to remote server"
$applicationCacheEnabled
[1] FALSE

$rotatable
[1] FALSE

$mobileEmulationEnabled
[1] FALSE

$networkConnectionEnabled
[1] TRUE

$chrome
$chrome$chromedriverVersion
[1] "2.29.461571 (8a88bbe0775e2a23afda0ceaf2ef7ee74e822cc5)"

$chrome$userDataDir
[1] "/tmp/.org.chromium.Chromium.iH4ir2"


$takesHeapSnapshot
[1] TRUE

$pageLoadStrategy
[1] "normal"

$unhandledPromptBehavior
[1] ""

$databaseEnabled
[1] FALSE

$handlesAlerts
[1] TRUE

$hasTouchScreen
[1] TRUE

$version
[1] "58.0.3029.110"

$platform
[1] "LINUX"

$browserConnectionEnabled
[1] FALSE

$nativeEvents
[1] TRUE

$acceptSslCerts
[1] TRUE

$webdriver.remote.sessionid
[1] "e8aed675-febf-40c9-8eff-4f740a17291f"

$locationContextEnabled
[1] TRUE

$webStorageEnabled
[1] TRUE

$browserName
[1] "chrome"

$takesScreenshot
[1] TRUE

$javascriptEnabled
[1] TRUE

$cssSelectorsEnabled
[1] TRUE

$unexpectedAlertBehaviour
[1] ""

$id
[1] "e8aed675-febf-40c9-8eff-4f740a17291f"
remDr <- rD[['client']]
remDr$navigate(Page)
WebElem <- remDr$findElement(using = 'xpath', value = XpathValue)
URL <- as.character(WebElem$getElementAttribute('href'))
remDr$close()
rD[['server']]$stop()
[1] TRUE
rm(rD)
rm(remDr)
rm(WebElem)
unreg_emp <- data.table(read.xlsx(URL))

changing accented characters into non-accented ones in the downloaded data

colnames(unreg_emp) <- gsub('(', '_', gsub(')', '', gsub(' ', '', stri_trans_totitle(gsub('\\.', ' ', stri_trans_general(colnames(unreg_emp), 'Latin-ASCII'))))), fixed = TRUE)
unreg_emp[, ':=' (AdozoNeve = stri_trans_general(AdozoNeve, 'Latin-ASCII'), Szekhely_Lakcim = stri_trans_general(Szekhely_Lakcim, 'Latin-ASCII'))]

saving

file_out = paste(getwd(), '/_tmp/unreg_emp.RData', sep = '')
fwrite(unreg_emp, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(unreg_emp[sample(.N, 5)], split.table = 200)

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sorszam            AdozoNeve                   Szekhely_Lakcim          Adoszam_AdoazonositoJel   JogsertestMegallapitoHatarozatKelte   JogsertestMegallapitoHatarozatVegrehajthatovaValasanakNapja 
--------- ------------------------------ ------------------------------ ------------------------- ------------------------------------- -------------------------------------------------------------
   262          Balint Gastro Kft.       4624 Tiszabezded, Dozsa Gy. u.       25088681-2-15                       42328                                             42378                            
                                                      39.                                                                                                                                            

  1027            Equinvest Kft.           7100 Szekszard, Rakoczi u.         23093263-2-08                       42199                                             42244                            
                                                      134.                                                                                                                                           

  1805    JERI Kereskedelmi Bt. ,,kt.a." 1035 Budapest, Miklos utca 13.       21312818-2-41                       42634                                             42670                            
                                                 VIII. em. 42.                                                                                                                                       

  3205           RESTO FOOD Kft.          3281 Karacsond, Nagy Imre ut        14818013-2-10                       41886                                             41909                            
                                                       2.                                                                                                                                            

  1232        Folcz Kereskedelmi es        2900 Komarom, Diofa ut 16.         20326973-2-11                       42347                                             42355                            
                 Szolgaltato Bt.                                                                                                                                                                     
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
-----------------
 KozzetetelNapja 
-----------------
      42422      

      42268      

      42751      

      42144      

      42422      
-----------------

extract the necessary data out of unreg_emp

unreg_emp <- unreg_emp[, names(unreg_emp)[4], with = FALSE]
unreg_emp[, unreg_emp := TRUE]
unreg_emp[, Adoszam := substr(stri_trim(Adoszam_AdoazonositoJel), 1, 8)]
unreg_emp[, Adoszam_AdoazonositoJel := NULL]
unreg_emp <- unique(unreg_emp)

saving

file_out = paste(getwd(), '/_tmp/unreg_emp.RData', sep = '')
fwrite(unreg_emp, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(unreg_emp[sample(.N, 5)], split.table = 200)

---------------------
 unreg_emp   Adoszam 
----------- ---------
   TRUE     13163602 

   TRUE     66485494 

   TRUE     24722412 

   TRUE     24906120 

   TRUE     13354309 
---------------------

Enriching Data from internal and external sources

creating a new, modified instance of company list - importing

file_in = paste(getwd(), '/_tmp/company_list.RData', sep = '')
company_list_1 <- data.table(fread(file_in))

creating a new, modified instance of company list - creating new columns

company_list_1[nchar(sub(' .*', '', stri_trim(Szekhely))) == 4, IrSzam := substr(sub(' .*', '', stri_trim(Szekhely)), 1, 4)]
company_list_1[Adoszam == substr(AdoszamHosszu,1 , 8) & nchar(AdoszamHosszu) == 13, AFAKod := substr(AdoszamHosszu, 10, 10)]
company_list_1[Adoszam == substr(AdoszamHosszu,1 , 8) & nchar(AdoszamHosszu) == 13, NAVTeruletKod := substr(AdoszamHosszu, 12, 13)]
company_list_1[, AdoszamHosszu := NULL]

let’s check a sample of the data

pander(company_list_1[sample(.N, 5)], split.table = 200)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Adoszam   SajatToke   MerlegEredmeny   Arbevetel             Szekhely             SzekhelyVaros                 FotevekenysegText                  Letszam   Cegkora   AktivPrivatTulajdonos 
--------- ----------- ---------------- ----------- ------------------------------ --------------- ------------------------------------------------ --------- --------- -----------------------
24319687       0             0              0          1037 Budapest Bokor 9         Budapest           SzakmaiTudomanyosMuszakiTevekenyseg            1         3                1           

24128595    582000         -49000           0       1055 Budapest Szentistvan 1      Budapest                  InformacioKommunikacio                 NA         4                2           

23451751    3556000        496000       19775000      2051 Biatorbagy Szily 2       Biatorbagy              KereskedelemGepjarmujavitas                2         5                1           

12942503    3802000        614000        3312000   7720 Lovaszheteny Szabadsag 17  Lovaszheteny                      NemIsmert                         1        14                2           

23156470   -1519000        108000        8490000        7624 Pecs Rakoczi 24           Pecs       AdminisztrativEsSzolgaltatastTamogatoTevekenyseg     2         6                1           
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
--------------------------------------------------------------------------------
 AktivTulajdonosSzam   Cegforma   BankiUgyfel   IrSzam   AFAKod   NAVTeruletKod 
--------------------- ---------- ------------- -------- -------- ---------------
          1              Kft         FALSE       1037      2           41       

          2              Kft         FALSE       1055      2           41       

          1              Kft         FALSE       2051      2           13       

          2              Kft         FALSE       7720      1           02       

          1              Kft         FALSE       7624      2           02       
--------------------------------------------------------------------------------

let’s denote those companies which used unregistered employees - right join

company_list_1[, Adoszam := as.character(Adoszam)]
setkey(unreg_emp, Adoszam)
setkey(company_list_1, Adoszam)
company_list_1 <- unreg_emp[company_list_1]

replacing NA with FALSE in unreg_emp column

company_list_1[is.na(unreg_emp), unreg_emp := FALSE]

let’s check a sample of the data

pander(company_list_1[unreg_emp, ][sample(.N, 5)], split.table = 200)

----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 unreg_emp   Adoszam   SajatToke   MerlegEredmeny   Arbevetel          Szekhely          SzekhelyVaros                 FotevekenysegText                  Letszam   Cegkora   AktivPrivatTulajdonos 
----------- --------- ----------- ---------------- ----------- ------------------------ --------------- ------------------------------------------------ --------- --------- -----------------------
   TRUE     24090524       0             0              0       1191 Budapest Hamvas 7     Budapest     AdminisztrativEsSzolgaltatastTamogatoTevekenyseg    NA         4                2           

   TRUE     25030011    3430000        206000       11017000    1039 Budapest Zsirai 2     Budapest                   SzallitasRaktarozas                    3         2                1           

   TRUE     12649372   -19187000      -2706000       9524000   3521 Miskolc Szacsvai 12     Miskolc               KereskedelemGepjarmujavitas               NA        15                1           

   TRUE     13636445    608000        -3099000      58956000      7624 Pecs Xaver 19         Pecs             Szallashely-SzolgaltatasVendeglatas           NA        11                1           

    NA      13628596      NA             NA            NA                 NA                  NA                               NA                           NA        NA               NA           
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
----------------------------------------------------------------------------------------------
 AktivTulajdonosSzam   Cegforma   BankiUgyfel   IrSzam   AFAKod   NAVTeruletKod   i.unreg_emp 
--------------------- ---------- ------------- -------- -------- --------------- -------------
          2              Kft         FALSE       1191      2           43            TRUE     

          1              Kft         FALSE       1039      2           41            TRUE     

          1              Kft         FALSE       3521      2           05            TRUE     

          1              Kft         FALSE       7624      2           02            TRUE     

         NA               NA          NA          NA       NA          NA            TRUE     
----------------------------------------------------------------------------------------------

joining latitude and longitude data of postcodes to company list - right join

setkey(postcode_list, IrSzam)
setkey(company_list_1, IrSzam)
company_list_1 <- postcode_list[company_list_1]
i <- company_list_1[is.na(Lat), .N]
j <- company_list_1[, .N]
pander(paste('There are', i, 'clients out of', j, 'without geocode proxy', sep = ' '), split.table = 200)
There are 781 clients out of 435439 without geocode proxy

Let’s check with eyeballing in which postcode area are the current clients’ hq

dt <- copy(data.table(company_list_1[!is.na(Lat), .N, by = .(BankiUgyfel, Lat, Lon)]))
ggmap(Hungary) +
  geom_point(data = dt,
             aes(x = Lon,
                 y = Lat,
                 color = BankiUgyfel,
                 size = N),
             alpha = 0.7,
             stroke = 1.2,
             shape = 1) +
  ggtitle('Number of Companies per Postcode Areas on Hungary')

let’s check a sample of the data

pander(company_list_1[!is.na(Lat), ][sample(.N, 5)], split.table = 200)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 IrSzam     Megye        Varos      Lat   Lon   unreg_emp   Adoszam   SajatToke   MerlegEredmeny   Arbevetel           Szekhely            SzekhelyVaros           FotevekenysegText           Letszam 
-------- ----------- ------------- ----- ----- ----------- --------- ----------- ---------------- ----------- --------------------------- --------------- ----------------------------------- ---------
  2340      Pest     Kiskunlachaza 47.2  19.02    FALSE    23867747   -1373000        321000        971000    2340 Kiskunlachaza Dozsa 77  Kiskunlachaza            Feldolgozoipar               NA    

  2030      Pest          Erd      47.37 18.93    FALSE    10902941   -2279000       5862000        5426000       2030 Erd Terasz 56            Erd                    Epitoipar                  1    

  4033   Hajdu-Bihar   Debrecen    47.77 21.24    FALSE    14513017   -2495000        238000        4057000     4033 Debrecen Gyorgy 9       Debrecen     SzakmaiTudomanyosMuszakiTevekenyseg    NA    

  2030      Pest          Erd      47.37 18.93    FALSE    22701015    8300000       -490000           0         2030 Erd Erzsebet 46           Erd                     Oktatas                  NA    

  1193    Budapest     Budapest    47.5  19.08    FALSE    22725134   -1164000       -467000        4318000     1193 Budapest Klapka 2       Budapest             SzallitasRaktarozas            NA    
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
---------------------------------------------------------------------------------------------------------
 Cegkora   AktivPrivatTulajdonos   AktivTulajdonosSzam   Cegforma   BankiUgyfel   AFAKod   NAVTeruletKod 
--------- ----------------------- --------------------- ---------- ------------- -------- ---------------
    5                2                      2              Kft         TRUE         2           13       

   23                2                      2              Kft         FALSE        2           13       

    8                2                      2              Kft         FALSE        1           09       

    6                2                      2               Bt         FALSE        1           13       

    6                1                      1              Kft         FALSE        2           43       
---------------------------------------------------------------------------------------------------------

joining latitude and longitude data of postcodes to branch postcode list - right join

setkey(branch_zip_list, IrSzam)
setkey(postcode_list, IrSzam)
branch_postcode_list <- postcode_list[branch_zip_list]

saving

file_out = paste(getwd(), '/_tmp/branch_postcode_list.RData', sep = '')
fwrite(branch_postcode_list, 
       file = file_out,
       nThread = getDTthreads())

Let’s check with eyeballing in which postcode area are the branches on the bank

ggmap(Hungary) +
  geom_point(data = branch_postcode_list,
             aes(x = Lon,
                 y = Lat),
             size = 1,
             color = 'green',
             alpha = 0.7,
             stroke = 1.2,
             shape = 1) +
  ggtitle('Bank Branches All Over Hungary')

let’s check a sample of the data

pander(branch_postcode_list[sample(.N, 5)], split.table = 200)

--------------------------------------------
 IrSzam     Megye       Varos     Lat   Lon 
-------- ----------- ----------- ----- -----
  1211    Budapest    Budapest   47.5  19.08

  6230   Bacs-Kiskun Soltvadkert 46.58 19.4 

  6413   Bacs-Kiskun Kunfeherto  46.35 19.42

  6088   Bacs-Kiskun   Apostag   46.88 18.95

  6331   Bacs-Kiskun    Fokto    46.52 18.92
--------------------------------------------

deallocating memory!

rm(Hungary)

calculating distance proxy between firms address and a the nearest branch of bank

postcode_geocode_list <- copy(data.table(unique(company_list_1[!is.na(Lat), .(IrSzam, Lat, Lon)])))
setkey(postcode_geocode_list, IrSzam)
dist_matrix <- copy(data.table(postcode_geocode_list[, IrSzam]))
colnames(dist_matrix) <- 'IrSzam'
dist_matrix[, LegkozFiokTavProxy := 10000000]
for (i in 1:nrow(postcode_geocode_list)) {
  for (j in 1:nrow(branch_postcode_list)) {
    dist_matrix[i, branch_postcode_list[j, IrSzam]] <- gdist(lon.1 = postcode_geocode_list[i, Lon],
                                                             lat.1 = postcode_geocode_list[i, Lat],
                                                             lon.2 = branch_postcode_list[j, Lon],
                                                             lat.2 = branch_postcode_list[j, Lat],
                                                             units = 'km')
  }
  dist_matrix[i, LegkozFiokTavProxy := min(as.numeric(dist_matrix[i, 3:(ncol(dist_matrix)), with = FALSE]), na.rm = TRUE)]
}

saving

file_out = paste(getwd(), '/_tmp/dist_matrix.RData', sep = '')
fwrite(dist_matrix, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the distance matrix data

pander(dist_matrix[sample(.N, 5)], split.table = 200)

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 IrSzam   LegkozFiokTavProxy   1211   2300   2315   2336   2340   2344   6000   6031   6032   6033   6041   6044   6045   6050   6060   6065   6066   6077   6078   6087   6088   6097   6100   6112 
-------- -------------------- ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------
  3378          96.52         112.5  135.9  124.4   122   129.6  137.4  110.2  103.1  110.6  118.9  120.5  119.9  114.2  110.3  96.52  105.5  111.3  137.2  130.5  149.4  153.7  127.9  126.2  132.6 

  4063          121.3         171.1   188   179.9  176.1  182.1  187.2  142.9  132.5  139.8  149.3  160.5  162.3  157.6  151.6  121.3  131.9  136.1  171.9  162.8  195.3  199.1  170.7  151.6  153.3 

  2522          33.23         33.23  58.31  42.77  50.13  56.61   68.3  117.4  124.2  125.1  123.5  99.46  92.59   89.6  95.85  132.6  130.4  134.3  117.1  121.3  85.79  88.87  91.69  136.5  148.3 

  8948          169.3         202.1  179.5   189   191.8  185.4  180.9  236.9  248.5  243.5  235.2  214.1  210.1  213.4  220.7  261.3  252.4  251.8  212.6  222.8  176.4  173.5  202.1  241.6  248.8 

  9985          210.7         231.1  213.5  220.4  224.3   219   216.3  275.1  286.5  282.1  274.2  251.9  247.3   250   257.5  299.2  290.8  290.7  252.3  262.4  214.2  211.7  239.6  281.5  289.3 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 6113   6114   6115   6120   6131   6133   6134   6135   6211   6230   6237   6300   6320   6323   6325   6326   6328   6331   6332   6333   6334   6336   6345   6346   6347   6353   6400   6413   6421   6422   6423   6449   6500 
------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------ ------
136.7  134.5  124.4  152.4  148.1  143.9  151.4   157   147.9  156.2  167.3   179    156   157.3  154.5  163.5  169.4   184   179.8   178   175.7  173.1  193.3  200.4  204.2   193   166.7  177.3  181.6  186.3  186.7   192   211.1 

 160   163.4  153.7   175   173.7  167.8  170.4  175.7  181.7   188   200.3  216.1  198.8  201.5  194.8  203.6  208.6  221.6  218.5  217.6  215.5  209.4  225.4  232.3  235.7  227.3  193.4  203.6  205.1  207.7  206.3  216.3  241.6 

144.2  130.9   127   152.1  143.4  145.6  159.5  163.1  122.7  131.5  133.2  127.6  99.02  94.32  106.3  110.8  116.2  128.7  123.2  119.4  117.7  126.8  150.2  155.3  158.8  143.9  149.3  155.8  165.3  172.9  177.4  169.7   166  

240.5  228.4  233.7  234.6  228.7  235.4  244.6  243.6  207.1  207.4   197   176.6  177.8   173   185.3  178.8  176.8  171.5  170.9  169.3  170.4  183.1  185.6   183   182.8  177.9  216.3  212.5  220.2  225.3  231.2  213.9  183.9 

281.2  268.6  273.2  276.3  269.9  276.4  286.3  285.6  247.6  248.6  238.7  218.5   217   211.8  225.1  219.2  217.8  213.7  212.6  210.7  211.7  224.8  228.8  226.6  226.5  220.9  258.5  255.3  263.3  268.6  274.6  257.4  227.9 
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

let’s enriching the company list data with nearest branch distance proxy

dt <- copy(data.table(dist_matrix[, 1:2]))
setkey(dt, IrSzam)
setkey(company_list_1, IrSzam)
company_list_1 <- dt[company_list_1]

saving

file_out = paste(getwd(), '/_tmp/company_list_1.RData', sep = '')
fwrite(company_list_1, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(company_list_1[sample(.N, 5)], split.table = 200)

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 IrSzam   LegkozFiokTavProxy         Megye               Varos        Lat   Lon   unreg_emp   Adoszam   SajatToke   MerlegEredmeny   Arbevetel           Szekhely             SzekhelyVaros  
-------- -------------------- -------------------- ----------------- ----- ----- ----------- --------- ----------- ---------------- ----------- --------------------------- -----------------
  2162          24.61                 Pest             Orbottyan     47.68 19.27    FALSE    14614352    8013000       2971000       27760000    2162 Orbottyan Dozsa 9547      Orbottyan    

  1088          1.217               Budapest           Budapest      47.49 19.09    FALSE    26851284       0             0              0        1088 Budapest Muzeum 15       Budapest     

  2310           4.21                 Pest         Szigetszentmiklos 47.35 19.05    FALSE    14142424     39000         -8000            0        2310 Szigetszentmiklos    Szigetszentmiklos
                                                                                                                                                       Sargarozsa 12                         

  5400          41.26         Jasz-Nagykun-Szolnok      Mezotur       47   20.63    FALSE    11508052   33882000       -241000        9157000     5400 Mezotur Kossuth 6         Mezotur     

  1149          3.623               Budapest           Budapest      47.51 19.13    FALSE    13121927   -6565000       -2500000      15129000   1149 Budapest Nagylajos 134     Budapest     
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
------------------------------------------------------------------------------------------------------------------------------------------------------
        FotevekenysegText           Letszam   Cegkora   AktivPrivatTulajdonos   AktivTulajdonosSzam   Cegforma   BankiUgyfel   AFAKod   NAVTeruletKod 
---------------------------------- --------- --------- ----------------------- --------------------- ---------- ------------- -------- ---------------
            NemIsmert                  3         8                1                      1              Kft         FALSE        2           13       

      InformacioKommunikacio          NA        23                2                      2               Bt         FALSE        3           42       

             Oktatas                  NA         9                2                      2              Kft         FALSE        1           13       

Human-EgeszsegugyiSzocialisEllatas    NA        18                1                      1              Kft         FALSE        2           16       

            NemIsmert                  4        13               NA                     NA              Kft         FALSE        2           42       
------------------------------------------------------------------------------------------------------------------------------------------------------

deallocating memory

rm(dist_matrix)

let’ s denote whether parent companies are clients also or not

dt <- company_list_1[BankiUgyfel == TRUE & nchar(Adoszam) == 8, .(BankiUgyfel, Adoszam)]
setnames(dt, 'Adoszam', 'AdoszamTulaj')
setnames(dt, 'BankiUgyfel', 'TulajCegesBankiUgyfel')
owner_company_list_1 <- copy(data.table(owner_company_list))

right join

setkey(dt, AdoszamTulaj)
setkey(owner_company_list_1, AdoszamTulaj)
owner_company_list_1 <- dt[owner_company_list_1]
owner_company_list_1[is.na(TulajCegesBankiUgyfel), TulajCegesBankiUgyfel := FALSE]

saving

file_out = paste(getwd(), '/_tmp/owner_company_list_1.RData', sep = '')
fwrite(owner_company_list_1, 
       file = file_out,
       nThread = getDTthreads())

let’s check the data

pander(owner_company_list_1[, .N, by = (TulajCegesBankiUgyfel)], split.table = 200)

-----------------------------
 TulajCegesBankiUgyfel    N  
----------------------- -----
         FALSE          32651

         TRUE            528 
-----------------------------

let’s count the number of those owner companies which are customers of the Bank by affiliate companies

dt <- copy(data.table(owner_company_list_1[TulajCegesBankiUgyfel == TRUE, ]))
dt <- data.table(sqldf('SELECT Adoszam
                              ,COUNT(DISTINCT TulajCegesBankiUgyfel) AS TulajCegesBankiUgyfelNr
                        FROM  dt
                        GROUP BY Adoszam'))

let’s check a sample of the data

pander(dt[sample(.N, 5)], split.table = 200)

-----------------------------------
 Adoszam   TulajCegesBankiUgyfelNr 
--------- -------------------------
12634402              1            

23113165              1            

25414905              1            

13641346              1            

12459418              1            
-----------------------------------

let’s join the company list with the modified owner company list

setkey(dt, Adoszam)
setkey(company_list_1, Adoszam)
company_list_1 <- dt[company_list_1]
company_list_1[is.na(TulajCegesBankiUgyfelNr), TulajCegesBankiUgyfelNr := 0]

saving

file_out = paste(getwd(), '/_tmp/company_list_1.RData', sep = '')
fwrite(company_list_1, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(company_list_1[sample(.N, 5)], split.table = 200)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Adoszam   TulajCegesBankiUgyfelNr   IrSzam   LegkozFiokTavProxy        Megye          Varos     Lat   Lon   unreg_emp   SajatToke   MerlegEredmeny   Arbevetel           Szekhely         
--------- ------------------------- -------- -------------------- ----------------- ----------- ----- ----- ----------- ----------- ---------------- ----------- --------------------------
22674784              0               9022          96.76         Gyor-Moson-Sopron    Gyor     47.61 17.78    FALSE      3353000        575000       39936000      9022 Gyor Bajcsy 43    

14249734              0               1151            0               Budapest       Budapest   47.5  19.08    FALSE     30249000       23601000      293416000   1151 Budapest Szekely 11 

13981604              0               2039          17.97               Pest        Pusztazamor 47.4  18.78    FALSE         0             0              0       2039 Pusztazamor Erkel 2 

24695181              0               4262          160.6            Hajdu-Bihar     Nyiracsad  47.6  21.98    FALSE       1000         -194000        653000    4262 Nyiracsad Kossuth 114

28550219              0               1031          8.404             Budapest       Budapest   47.57 19.05    FALSE     -3740000        -2000            0        1031 Budapest Varsa 3   
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------
 SzekhelyVaros           FotevekenysegText           Letszam   Cegkora   AktivPrivatTulajdonos   AktivTulajdonosSzam   Cegforma   BankiUgyfel   AFAKod   NAVTeruletKod 
--------------- ----------------------------------- --------- --------- ----------------------- --------------------- ---------- ------------- -------- ---------------
     Gyor       SzakmaiTudomanyosMuszakiTevekenyseg     4         6                1                      1              Kft         FALSE        2           08       

   Budapest         KereskedelemGepjarmujavitas         7         8                2                      2              Kft         FALSE        2           42       

  Pusztazamor            Ingatlanugyletek              NA         9               NA                     NA                          FALSE        2           13       

   Nyiracsad        KereskedelemGepjarmujavitas        NA         3                1                      1              Kft         FALSE        2           09       

   Budapest                  NemIsmert                 NA        22                2                      2               Bt         FALSE        1           41       
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------

deallocating memory

rm(owner_company_list, owner_company_list_1)

prerequisite of detection of Adoszam values being unique is to order them by that column

company_list_1 <- company_list_1[order(Adoszam)]

let’s check column values validity again

ct <- check_that(company_list_1, 
                 valid_notation_BankiUgyfel = BankiUgyfel == 0
                                            | BankiUgyfel == 1,
                 distinct_Adoszam = rle(sort(Adoszam))[[1]] == 1,
                 complete_Adoszam = stri_length(Adoszam) == 8,
                 initcap_Cegforma = Cegforma == stri_trans_totitle(Cegforma),
                 valid_AktivTulajdonosSzam = AktivTulajdonosSzam >= 1,
                 valid_AktivPrivatTulajdonos = AktivTulajdonosSzam >= AktivPrivatTulajdonos,
                 filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos = stri_length(AktivTulajdonosSzam) >= 1
                                                                      & stri_length(AktivPrivatTulajdonos) >= 1,
                 not_negative_Cegkora = Cegkora >= 0,
                 under_UCL_Cegkora = Cegkora <= mean(Cegkora, na.rm = TRUE) + 3 * sd(Cegkora, na.rm = TRUE),
                 valid_Letszam = Letszam >= 1,
                 filled_FotevekenysegText = stri_length(FotevekenysegText) >= 1,
                 valid_postcode = nchar(sub(' .*', '', stri_trim(Szekhely))) == 4,
                 valid_SzekhelyVaros = stri_length(SzekhelyVaros) >= 2
                                     & grepl('\\d', SzekhelyVaros) == FALSE,
                 initcap_SzekhelyVaros = SzekhelyVaros == stri_trans_totitle(SzekhelyVaros), 
                 valid_Szekhely = stri_length(Szekhely) >= 11
                                & grepl('\\D', substr(stri_trim(Szekhely), 1, 4)) == FALSE
                                & grepl('\\^', Szekhely) == FALSE,
                 initcap_Szekhely = Szekhely == stri_trans_totitle(Szekhely), 
                 filled_Arbevetel = stri_length(Arbevetel) >= 1,
                 filled_MerlegEredmeny = stri_length(MerlegEredmeny) >= 1,
                 filled_SajatToke = stri_length(SajatToke) >= 1,
                 filled_FotevekenysegText_and_AktivTulajdonosSzam_and_Cegforma = stri_length(FotevekenysegText) >= 1
                                                                               & stri_length(AktivTulajdonosSzam) >= 1
                                                                               & stri_length(Cegforma) >= 1,
                 filled_Lat_Lon = !is.na(Lat)
                                & !is.na(Lon),
                 filled_unreg_emp = !is.na(unreg_emp),
                 filled_AFAKod = !is.na(AFAKod),
                 filled_Megye = !is.na(Megye),
                 filled_TulajCegesBankiUgyfelNr = !is.na(TulajCegesBankiUgyfelNr)
                 )
dt <- data.table(summary(ct))
dt <- dt[, c(1:5, 8)]

let’s see the validity of the data

pander(dt, split.table = 200)

--------------------------------------------------------------------------------------------------------------------------------
                            rule                               items   passes   fails   nNA               expression            
------------------------------------------------------------- ------- -------- ------- ------ ----------------------------------
                 valid_notation_BankiUgyfel                   435439   435439     0      0      BankiUgyfel == 0 | BankiUgyfel  
                                                                                                             == 1               

                      distinct_Adoszam                        435439   435439     0      0       rle(sort(Adoszam))[[1]] == 1   

                      complete_Adoszam                        435439   435439     0      0        stri_length(Adoszam) == 8     

                      initcap_Cegforma                        435439   435439     0      0               Cegforma ==            
                                                                                                 stri_trans_totitle(Cegforma)   

                  valid_AktivTulajdonosSzam                   435439   419130     0    16309       AktivTulajdonosSzam >= 1     

                 valid_AktivPrivatTulajdonos                  435439   419130     0    16309        AktivTulajdonosSzam >=      
                                                                                                    AktivPrivatTulajdonos       

    filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos      435439   419130     0    16309   stri_length(AktivTulajdonosSzam) 
                                                                                                            >= 1 &              
                                                                                              stri_length(AktivPrivatTulajdonos)
                                                                                                             >= 1               

                    not_negative_Cegkora                      435439   435055     0     384              Cegkora >= 0           

                      under_UCL_Cegkora                       435439   434151    904    384     Cegkora <= mean(Cegkora, na.rm  
                                                                                                  = TRUE) + 3 * sd(Cegkora,     
                                                                                                        na.rm = TRUE)           

                        valid_Letszam                         435439   122585     0    312854            Letszam >= 1           

                  filled_FotevekenysegText                    435439   435439     0      0      stri_length(FotevekenysegText)  
                                                                                                             >= 1               

                       valid_postcode                         435439   435426    13      0           nchar(sub(" .*", "",       
                                                                                                  stri_trim(Szekhely))) == 4    

                     valid_SzekhelyVaros                      435439   435430     9      0      stri_length(SzekhelyVaros) >=   
                                                                                                       2 & grepl("\\d",         
                                                                                                   SzekhelyVaros) == FALSE      

                    initcap_SzekhelyVaros                     435439   435439     0      0             SzekhelyVaros ==         
                                                                                              stri_trans_totitle(SzekhelyVaros) 

                       valid_Szekhely                         435439   435422    17      0      stri_length(Szekhely) >= 11 &   
                                                                                                         grepl("\\D",           
                                                                                                substr(stri_trim(Szekhely), 1,  
                                                                                                 4)) == FALSE & grepl("\\^",    
                                                                                                      Szekhely) == FALSE        

                      initcap_Szekhely                        435439   435439     0      0               Szekhely ==            
                                                                                                 stri_trans_totitle(Szekhely)   

                      filled_Arbevetel                        435439   435439     0      0       stri_length(Arbevetel) >= 1    

                    filled_MerlegEredmeny                     435439   435439     0      0      stri_length(MerlegEredmeny) >=  
                                                                                                              1                 

                      filled_SajatToke                        435439   435439     0      0       stri_length(SajatToke) >= 1    

filled_FotevekenysegText_and_AktivTulajdonosSzam_and_Cegforma 435439   387236   35205  12998    stri_length(FotevekenysegText)  
                                                                                                            >= 1 &              
                                                                                               stri_length(AktivTulajdonosSzam) 
                                                                                                 >= 1 & stri_length(Cegforma)   
                                                                                                             >= 1               

                       filled_Lat_Lon                         435439   434658    781     0        !is.na(Lat) & !is.na(Lon)     

                      filled_unreg_emp                        435439   435439     0      0            !is.na(unreg_emp)         

                        filled_AFAKod                         435439   434828    611     0              !is.na(AFAKod)          

                        filled_Megye                          435439   434658    781     0              !is.na(Megye)           

               filled_TulajCegesBankiUgyfelNr                 435439   435439     0      0     !is.na(TulajCegesBankiUgyfelNr)  
--------------------------------------------------------------------------------------------------------------------------------

Imputating Data

Some AktivPrivatTulajdonos, AktivTulajdonosSzam values are missing / I impute them with the median value in each Megye with same FotevekenysegText and AFAKod

subsetting into dt

dt <- copy(data.table(company_list_1[!is.na(AFAKod)
                                   & !is.na(Megye)
                                   & !is.na(FotevekenysegText), 
                                   .(Adoszam,
                                     AktivPrivatTulajdonos,
                                     AktivTulajdonosSzam,
                                     AFAKod,
                                     Megye,
                                     FotevekenysegText)]))

factorization of categorical variables

dt[, ':=' (AFAKod = as.factor(AFAKod),
           Megye = as.factor(Megye),
           FotevekenysegText = as.factor(FotevekenysegText))]

checking the number of missing values of AktivPrivatTulajdonos

n <- dt[is.na(AktivPrivatTulajdonos), .N]
pandoc.header(paste('The prior number of missing AktivPrivatTulajdonos values: ', n, sep = ''))

# The prior number of missing AktivPrivatTulajdonos values: 15942

imputation

formula <- AktivPrivatTulajdonos ~ AFAKod + Megye + FotevekenysegText
dt <- impute_median(dt, formula)
Sys.sleep(3)

rounding the imputed values

dt[!is.na(AktivPrivatTulajdonos), AktivPrivatTulajdonos := round(AktivPrivatTulajdonos, 0)]

checking the number of missing values of AktivTulajdonosSzam

n <- dt[is.na(AktivTulajdonosSzam), .N]
pandoc.header(paste('The prior number of missing AktivTulajdonosSzam values: ', n, sep = ''))

# The prior number of missing AktivTulajdonosSzam values: 15942

imputation

formula <- AktivTulajdonosSzam ~ AFAKod + Megye + FotevekenysegText
dt <- impute_median(dt, formula)
Sys.sleep(3)

rounding the imputed values

dt[!is.na(AktivTulajdonosSzam), AktivTulajdonosSzam := round(AktivTulajdonosSzam, 0)]

subsetting

dt <- data.table(dt[, .(Adoszam, AktivPrivatTulajdonos, AktivTulajdonosSzam)])

initial preparation of company list 2

company_list_2 <- copy(data.table(company_list_1))

right join the imputed values into company_list_2

setkey(dt, Adoszam)
setkey(company_list_2, Adoszam)
company_list_2 <- data.table(dt[company_list_2])
company_list_2[is.na(AktivPrivatTulajdonos), AktivPrivatTulajdonos := i.AktivPrivatTulajdonos]
company_list_2[is.na(AktivTulajdonosSzam), AktivTulajdonosSzam := i.AktivTulajdonosSzam]
company_list_2[, ':=' (i.AktivPrivatTulajdonos = NULL, i.AktivTulajdonosSzam = NULL)]

saving

file_out = paste(getwd(), '/_tmp/company_list_2.RData', sep = '')
fwrite(company_list_2, 
       file = file_out,
       nThread = getDTthreads())

let’s check the imputation again

ct <- check_that(company_list_2,
                 filled_AktivTulajdonosSzam = stri_length(AktivTulajdonosSzam) >= 1,
                 filled_AktivPrivatTulajdonos = stri_length(AktivPrivatTulajdonos) >= 1,
                 filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos = stri_length(AktivTulajdonosSzam) >= 1
                                                                      & stri_length(AktivPrivatTulajdonos) >= 1,
                 valid_AktivPrivatTulajdonos = AktivTulajdonosSzam >= AktivPrivatTulajdonos,
                 filled_Cegforma = stri_length(Cegforma) >= 1,
                 filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos_and_Cegforma = stri_length(AktivTulajdonosSzam) >= 1
                                                                               & stri_length(AktivPrivatTulajdonos) >= 1
                                                                               & stri_length(Cegforma) >= 1
                 )
dt <- data.table(summary(ct))
dt <- dt[, c(1:5, 8)]

let’s see the validity of the data

pander(dt, split.table = 200)

-----------------------------------------------------------------------------------------------------------------------------------
                              rule                                 items   passes   fails   nNA              expression            
----------------------------------------------------------------- ------- -------- ------- ----- ----------------------------------
                   filled_AktivTulajdonosSzam                     435439   435071     0     368   stri_length(AktivTulajdonosSzam) 
                                                                                                                >= 1               

                  filled_AktivPrivatTulajdonos                    435439   435071     0     368  stri_length(AktivPrivatTulajdonos)
                                                                                                                >= 1               

      filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos        435439   435071     0     368   stri_length(AktivTulajdonosSzam) 
                                                                                                               >= 1 &              
                                                                                                 stri_length(AktivPrivatTulajdonos)
                                                                                                                >= 1               

                   valid_AktivPrivatTulajdonos                    435439   435071     0     368        AktivTulajdonosSzam >=      
                                                                                                       AktivPrivatTulajdonos       

                         filled_Cegforma                          435439   400234   35205    0       stri_length(Cegforma) >= 1    

filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos_and_Cegforma 435439   400149   35205   85    stri_length(AktivTulajdonosSzam) 
                                                                                                               >= 1 &              
                                                                                                 stri_length(AktivPrivatTulajdonos)
                                                                                                    >= 1 & stri_length(Cegforma)   
                                                                                                                >= 1               
-----------------------------------------------------------------------------------------------------------------------------------

deallocating memory!!

rm(company_list_1)

Some Cegforma values are missing / I impute them with the median value in each Megye with same FotevekenysegText and AFAKod

subsetting into dt

dt <- copy(data.table(company_list_2[!is.na(Adoszam)
                                   & !is.na(AktivPrivatTulajdonos)
                                   & !is.na(AktivTulajdonosSzam)
                                   & !is.na(Megye)
                                   & !is.na(FotevekenysegText)
                                   & !is.na(unreg_emp)
                                   & !is.na(AFAKod), 
                                   .(Adoszam,
                                     Cegforma,
                                     AktivPrivatTulajdonos,
                                     AktivTulajdonosSzam,
                                     Megye,
                                     FotevekenysegText,
                                     unreg_emp,
                                     AFAKod)]))

factorization of categorical variables

dt[, ':=' (Cegforma = as.factor(Cegforma),
           Megye = as.factor(Megye),
           FotevekenysegText = as.factor(FotevekenysegText),
           unreg_emp = as.factor(unreg_emp),
           AFAKod = as.factor(AFAKod)
           )]

imputation of Cegforma

formula <- Cegforma ~ AktivPrivatTulajdonos + AktivTulajdonosSzam + Megye + FotevekenysegText + unreg_emp + AFAKod
dt <- data.table(impute_mf(dt, formula))
  missForest iteration 1 in progress...done!
  missForest iteration 2 in progress...done!

joining

dt <- dt[, .(Adoszam, Cegforma)]
setkey(dt, Adoszam)
setkey(company_list_2, Adoszam)
company_list_2 <- data.table(dt[company_list_2])
company_list_2[is.na(Cegforma), Cegforma := i.Cegforma]
company_list_2[, i.Cegforma := NULL]

saving

file_out = paste(getwd(), '/_tmp/company_list_2.RData', sep = '')
fwrite(company_list_2, 
       file = file_out,
       nThread = getDTthreads())

let’s check the imputation again

ct <- check_that(company_list_2,
                 filled_AktivTulajdonosSzam = stri_length(AktivTulajdonosSzam) >= 1,
                 filled_AktivPrivatTulajdonos = stri_length(AktivPrivatTulajdonos) >= 1,
                 filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos = stri_length(AktivTulajdonosSzam) >= 1
                                                                      & stri_length(AktivPrivatTulajdonos) >= 1,
                 valid_AktivPrivatTulajdonos = AktivTulajdonosSzam >= AktivPrivatTulajdonos,
                 filled_Cegforma = stri_length(Cegforma) >= 1,
                 filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos_and_Cegforma = stri_length(AktivTulajdonosSzam) >= 1
                                                                               & stri_length(AktivPrivatTulajdonos) >= 1
                                                                               & stri_length(Cegforma) >= 1
                 )
temp <- data.table(summary(ct))
temp <- temp[, c(1:5, 8)]

let’s see the validity of the data

pander(temp, split.table = 200)

-----------------------------------------------------------------------------------------------------------------------------------
                              rule                                 items   passes   fails   nNA              expression            
----------------------------------------------------------------- ------- -------- ------- ----- ----------------------------------
                   filled_AktivTulajdonosSzam                     435439   435071     0     368   stri_length(AktivTulajdonosSzam) 
                                                                                                                >= 1               

                  filled_AktivPrivatTulajdonos                    435439   435071     0     368  stri_length(AktivPrivatTulajdonos)
                                                                                                                >= 1               

      filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos        435439   435071     0     368   stri_length(AktivTulajdonosSzam) 
                                                                                                               >= 1 &              
                                                                                                 stri_length(AktivPrivatTulajdonos)
                                                                                                                >= 1               

                   valid_AktivPrivatTulajdonos                    435439   435071     0     368        AktivTulajdonosSzam >=      
                                                                                                       AktivPrivatTulajdonos       

                         filled_Cegforma                          435439   400234   35205    0       stri_length(Cegforma) >= 1    

filled_AktivTulajdonosSzam_and_AktivPrivatTulajdonos_and_Cegforma 435439   400149   35205   85    stri_length(AktivTulajdonosSzam) 
                                                                                                               >= 1 &              
                                                                                                 stri_length(AktivPrivatTulajdonos)
                                                                                                    >= 1 & stri_length(Cegforma)   
                                                                                                                >= 1               
-----------------------------------------------------------------------------------------------------------------------------------

Let’s take a look at histograms of columns (variables) of company list 2

p11 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = log10(SajatToke + 1)),
                       fill = 'blue', 
                       position = 'identity',
                       binwidth = 0.3,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_SajatToke') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_SajatToke') +
        theme_igray()
Sys.sleep(2)  
p12 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = log10(MerlegEredmeny + 1)),
                       fill = 'green', 
                       position = 'identity',
                       binwidth = 0.3,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_MerlegEredmeny') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_MerlegEredmeny') +
        theme_igray()
Sys.sleep(2)  
p13 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = log10(Arbevetel + 1)),
                       fill = 'orange', 
                       position = 'identity',
                       binwidth = 0.3,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_Arbevetel') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_Arbevetel') +
        theme_igray()
Sys.sleep(2)
p14 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = log10(AktivPrivatTulajdonos +1)),
                       fill = 'red', 
                       position = 'identity',
                       binwidth = 0.1,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_AktivPrivatTulajdonos') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_AktivPrivatTulajdonos') +
        theme_igray()
Sys.sleep(2)
p15 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = AktivTulajdonosSzam),
                       fill = 'green', 
                       position = 'identity',
                       binwidth = 0.1,
                       alpha = I(0.7)) + 
        ggtitle('Histogram for log10_AktivTulajdonosSzam') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_AktivTulajdonosSzam') +
        scale_x_log10() +
        theme_igray()
Sys.sleep(2)
p16 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = TulajCegesBankiUgyfelNr),
                       fill = 'brown',
                       position = 'identity',
                       #binwidth = 1,
                       alpha = I(0.7)) +
        ggtitle('Histogram for TulajCegesBankiUgyfelNr') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('TulajCegesBankiUgyfelNr') +
        theme_igray()
Sys.sleep(2)
p17 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = log10(Cegkora + 1)),
                       fill = 'khaki',
                       position = 'identity',
                       binwidth = 0.2,
                       alpha = I(0.7)) +
        ggtitle('Histogram for log10_Cegkora') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_Cegkora') +
        theme_igray()
Sys.sleep(2)
p18 <- ggplot(company_list_2) + 
        geom_histogram(aes(x = log10(LegkozFiokTavProxy + 1)),
                       fill = 'magenta',
                       position = 'identity',
                       binwidth = 0.2,
                       alpha = I(0.7)) +
        ggtitle('Histogram for log10_LegkozFiokTavProxy') +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        xlab('log10_LegkozFiokTavProxy') +
        theme_igray()
Sys.sleep(2)
p19 <- ggplot(company_list_2) +
        geom_bar(aes(x = as.factor(Cegforma)),
                 fill = 'brown',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of Cegforma') +
        xlab('Cegforma') +
        theme_igray()
Sys.sleep(2)
p20 <- ggplot(company_list_2) +
        geom_bar(aes(x = as.factor(NAVTeruletKod)),
                 fill = 'purple',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of NAVTeruletKod') +
        xlab('NAVTeruletKod') +
        theme_igray()
Sys.sleep(2)
p21 <- ggplot(company_list_2) +
        geom_bar(aes(x = as.factor(Megye)),
                 fill = 'pink',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of Megye') +
        xlab('Megye') +
        theme_igray()
Sys.sleep(2)
p22 <- ggplot(company_list_2) +
        geom_bar(aes(x = as.factor(Varos)),
                 fill = 'green',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of Varos') +
        xlab('Varos') +
        theme_igray()
Sys.sleep(2)
p23 <- ggplot(company_list_2) +
        geom_bar(aes(x = as.factor(unreg_emp)),
                 fill = 'blue',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of unreg_emp') +
        xlab('unreg_emp') +
        theme_igray()
Sys.sleep(2)
p24 <- ggplot(company_list_2) +
        geom_bar(aes(x = as.factor(FotevekenysegText)),
                 fill = 'magenta',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of FotevekenysegText') +
        xlab('FotevekenysegText') +
        theme_igray()
Sys.sleep(2)
p25 <- ggplot(company_list_2) +
        geom_bar(aes(x = as.factor(AFAKod)),
                 fill = 'orange',
                 position = 'identity',
                 alpha = I(0.7)) +
        facet_grid(BankiUgyfel ~ ., scales = 'free', labeller = labeller(BankiUgyfel = c('TRUE' = 'Banki Ugyfel', 
                                                                                         'FALSE' = 'Not Banki Ugyfel'))) +
        ggtitle('Breakdown of AFAKod') +
        xlab('AFAKod') +
        theme_igray()
Sys.sleep(2)

without plotly in one grid:

# multiplot(p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25, cols = 2)

transforming into interactive graph

pp11 <- ggplotly(p11)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp11.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp11), file_out)

transforming into interactive graph

pp12 <- ggplotly(p12)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp12.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp12), file_out)

transforming into interactive graph

pp13 <- ggplotly(p13)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp13.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp13), file_out)

transforming into interactive graph

pp14 <- ggplotly(p14)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp14.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp14), file_out)

transforming into interactive graph

pp15 <- ggplotly(p15)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp15.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp15), file_out)

transforming into interactive graph

pp16 <- ggplotly(p16)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp16.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp16), file_out)

transforming into interactive graph

pp17 <- ggplotly(p17)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp17.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp17), file_out)

transforming into interactive graph

pp18 <- ggplotly(p18)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp18.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp18), file_out)

transforming into interactive graph

pp19 <- ggplotly(p19)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp19.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp19), file_out)

transforming into interactive graph

pp20 <- ggplotly(p20)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp20.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp20), file_out)

transforming into interactive graph

pp21 <- ggplotly(p21)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp21.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp21), file_out)

transforming into interactive graph

pp22 <- ggplotly(p22)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp22.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp22), file_out)

transforming into interactive graph

pp23 <- ggplotly(p23)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp23.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp23), file_out)

transforming into interactive graph

pp24 <- ggplotly(p24)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp24.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp24), file_out)

transforming into interactive graph

pp25 <- ggplotly(p25)
Sys.sleep(2)

saving

file_out = paste(getwd(), '/pp25.html', sep = '')
htmlwidgets::saveWidget(as.widget(pp25), file_out)

plotting

layout(pp11, dragmode = 'pan')

Sys.sleep(2)
layout(pp12, dragmode = 'pan')

Sys.sleep(2)
layout(pp13, dragmode = 'pan')

Sys.sleep(2)
layout(pp14, dragmode = 'pan')

Sys.sleep(2)
layout(pp15, dragmode = 'pan')

Sys.sleep(2)
layout(pp16, dragmode = 'pan')

Sys.sleep(2)
layout(pp17, dragmode = 'pan')

Sys.sleep(2)
layout(pp18, dragmode = 'pan')

Sys.sleep(2)
layout(pp19, dragmode = 'pan')

Sys.sleep(2)
layout(pp20, dragmode = 'pan')

Sys.sleep(2)
layout(pp21, dragmode = 'pan')

Sys.sleep(2)
layout(pp22, dragmode = 'pan')

Sys.sleep(2)
layout(pp23, dragmode = 'pan')

Sys.sleep(2)
layout(pp24, dragmode = 'pan')

Sys.sleep(2)
layout(pp25, dragmode = 'pan')

Sys.sleep(2)

moving those plotly files into tmp folder

filez <- list.files(getwd(), pattern = '^pp\\d+\\.html$')
sapply(filez, FUN = function(eachPath) {
  file.rename(from = eachPath,
              to = sub(pattern = 'pp',
                       replacement = './_tmp/pp',
                       eachPath))
  })
 pp1.html pp10.html pp11.html pp12.html pp13.html pp14.html pp15.html pp16.html pp17.html pp18.html pp19.html  pp2.html 
     TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE 
pp20.html pp21.html pp22.html pp23.html pp24.html pp25.html pp26.html  pp3.html  pp4.html  pp5.html  pp6.html  pp7.html 
     TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE      TRUE 
 pp8.html  pp9.html 
     TRUE      TRUE 

deallocating memory!!

rm(p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24, p25)
rm(pp1, pp2, pp3, pp4, pp5, pp6, pp7, pp8, pp9, pp10, pp11, pp12, pp13, pp14, pp15, pp16, pp17, pp18, pp19, pp20, pp21, pp22, pp23, pp24, pp25)

According to compared distributions, these may be predictor variables: SajatToke MerlegEredmeny AktivTulajdonosSzam TulajCegesBankiUgyfelNr Cegkora LegkozFiokTavProxy Cegforma NAVTeruletKod Megye Varos FotevekenysegText AFAKod

I put them and the target variable (BankiUgyfel) into a new data table and blended NAs.

I omitted the Varos variable, because I think that is too restrictive.

company_list_3 <- copy(data.table(company_list_2[!is.na(BankiUgyfel) &
                                                 !is.na(SajatToke) &
                                                 !is.na(MerlegEredmeny) &
                                                 !is.na(AktivTulajdonosSzam) &
                                                 !is.na(TulajCegesBankiUgyfelNr) &
                                                 !is.na(Cegkora) &
                                                 !is.na(LegkozFiokTavProxy) &
                                                 !is.na(Cegforma) &
                                                 !is.na(NAVTeruletKod) &
                                                 !is.na(Megye) &
                                                 !is.na(FotevekenysegText) &
                                                 !is.na(AFAKod), .(BankiUgyfel,
                                                                   SajatToke,
                                                                   MerlegEredmeny,
                                                                   AktivTulajdonosSzam,
                                                                   TulajCegesBankiUgyfelNr,
                                                                   Cegkora,
                                                                   LegkozFiokTavProxy,
                                                                   Cegforma,
                                                                   NAVTeruletKod,
                                                                   Megye,
                                                                   FotevekenysegText,
                                                                   AFAKod)]))

making factors from text variables

company_list_3[, ':=' (NAVTeruletKod = as.factor(NAVTeruletKod), Megye = as.factor(Megye), FotevekenysegText = as.factor(FotevekenysegText), AFAKod =as.factor(AFAKod))]
pander(str(company_list_3))
Classes ‘data.table’ and 'data.frame':  433668 obs. of  12 variables:
 $ BankiUgyfel            : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
 $ SajatToke              : num  6.47e+07 1.36e+10 1.31e+09 3.52e+09 1.01e+09 ...
 $ MerlegEredmeny         : num  6.04e+07 1.79e+09 -1.90e+07 5.48e+07 0.00 ...
 $ AktivTulajdonosSzam    : num  17 27 7 1 1 4 1 16 118 2 ...
 $ TulajCegesBankiUgyfelNr: int  0 0 0 0 0 0 0 0 0 0 ...
 $ Cegkora                : int  27 26 70 66 27 49 48 66 27 42 ...
 $ LegkozFiokTavProxy     : num  2.52 185.78 0 4.13 4.8 ...
 $ Cegforma               : Factor w/ 6 levels "","Bt","Egyeb",..: 1 6 6 6 6 6 6 6 1 4 ...
 $ NAVTeruletKod          : Factor w/ 37 levels "01","02","03",..: 30 8 32 32 30 13 30 33 31 31 ...
 $ Megye                  : Factor w/ 21 levels "Bacs-Kiskun",..: 6 9 6 6 6 15 6 6 6 6 ...
 $ FotevekenysegText      : Factor w/ 22 levels "AdminisztrativEsSzolgaltatastTamogatoTevekenyseg",..: 14 14 19 16 9 14 16 16 3 10 ...
 $ AFAKod                 : Factor w/ 9 levels "0","1","2","3",..: 3 3 3 3 3 3 3 3 3 3 ...
 - attr(*, ".internal.selfref")=<externalptr> 

saving

file_out = paste(getwd(), '/_tmp/company_list_3.RData', sep = '')
fwrite(company_list_3, 
       file = file_out,
       nThread = getDTthreads())

let’s check a sample of the data

pander(company_list_3[sample(.N, 5)], split.table = 200)

--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 BankiUgyfel   SajatToke   MerlegEredmeny   AktivTulajdonosSzam   TulajCegesBankiUgyfelNr   Cegkora   LegkozFiokTavProxy   Cegforma   NAVTeruletKod   Megye        FotevekenysegText       AFAKod 
------------- ----------- ---------------- --------------------- ------------------------- --------- -------------------- ---------- --------------- -------- --------------------------- --------
    FALSE          0             0                   1                       0                 7              0                            41        Budapest          Epitoipar             2    

    FALSE       3615000       -2710000               2                       0                10              0              Kft           43        Budapest   InformacioKommunikacio       2    

    FALSE      105326000      36399000               1                       0                16            8.322            Kft           13          Pest         Feldolgozoipar           2    

    FALSE      -12369000      -1173000               1                       0                16            71.21            Kft           10         Heves   KereskedelemGepjarmujavitas    2    

    FALSE       4602000       -3142000               1                       0                 4              0              Kft           43        Budapest KereskedelemGepjarmujavitas    2    
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

deallocating memory!!

rm(company_list_2)

Model building

splitting into train, test & validation subset

company_list_3 <- data.table(fread(paste(getwd(), '/_tmp/company_list_3.RData', sep = '')))
set.seed(20140407)
n <- nrow(company_list_3)
idx_train <- sample(1:n, 0.5*n)
idx_test <- sample(setdiff(1:n, idx_train), 0.25*n)
idx_valid <- sample(setdiff(setdiff(1:n, idx_train), idx_test), 0.25*n)
d_train <- company_list_3[idx_train,]
d_test <- company_list_3[idx_test,]
d_valid <- company_list_3[idx_valid,]
pandoc.header(paste('size of train subset: ', dim(d_train)[1], sep = ''))

# size of train subset: 216834
pandoc.header(paste('size of test subset: ', dim(d_test)[1], sep = ''))

# size of test subset: 108417
pandoc.header(paste('size of test subset: ', dim(d_valid)[1], sep = ''))

# size of test subset: 108417

deallocating memory

rm(company_list_3)

I chose random forest decision tree for predicting targetable companies and I used h2o since it is fast & powerful . RF is a swiss-army-knife method for classification. It means boostrapping data, building trees, aggregating with random subset of variable at each split.

computing optimal memory level for h2o

mem <- paste(as.character(round(as.numeric(system("awk '/Mem/ {print $2}' /proc/meminfo", intern=TRUE))[1]*0.8/1024/1024, 0)), 'g', sep = '')

initialize h2o Java server (R connects via REST) - setting RAM size & thread numbers to maximum available

h2o.init(max_mem_size = mem,
         nthreads = -1)
 Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         5 hours 13 minutes 
    H2O cluster version:        3.10.4.6 
    H2O cluster version age:    13 days  
    H2O cluster name:           H2O_started_from_R_sbudai_wzr283 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   5.21 GB 
    H2O cluster total cores:    4 
    H2O cluster allowed cores:  4 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        54321 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    R Version:                  R version 3.4.0 (2017-04-21) 

uploading data to H2O

dh2o_train <- as.h2o(d_train)

  |                                                                                                                        
  |                                                                                                                  |   0%
  |                                                                                                                        
  |==================================================================================================================| 100%
dh2o_test <- as.h2o(d_test)

  |                                                                                                                        
  |                                                                                                                  |   0%
  |                                                                                                                        
  |==================================================================================================================| 100%
dh2o_valid <- as.h2o(d_valid)

  |                                                                                                                        
  |                                                                                                                  |   0%
  |                                                                                                                        
  |==================================================================================================================| 100%

machine learning I played around with several settings and these seemed good enough

model <- h2o.randomForest(x = 2:ncol(dh2o_train), # predictor variables
                          y = 1, # target variable
                          training_frame = dh2o_train, # speaks for itself
                          model_id = 'BankiUgyfelModel', # being name of the model
                          nfolds = 5, # nr of folds for N-fold cross-validation
                          mtries = -1, # nr of variables randomly sampled as candidates at each split. sqrt(# of predictors)
                          ntrees = 30, # nr of trees
                          max_depth = 5 # maximum tree depth
                          )

  |                                                                                                                        
  |                                                                                                                  |   0%
  |                                                                                                                        
  |=                                                                                                                 |   1%
  |                                                                                                                        
  |===                                                                                                               |   2%
  |                                                                                                                        
  |===                                                                                                               |   3%
  |                                                                                                                        
  |======                                                                                                            |   6%
  |                                                                                                                        
  |===========                                                                                                       |  10%
  |                                                                                                                        
  |=================                                                                                                 |  15%
  |                                                                                                                        
  |===================                                                                                               |  17%
  |                                                                                                                        
  |====================                                                                                              |  18%
  |                                                                                                                        
  |=====================                                                                                             |  18%
  |                                                                                                                        
  |======================                                                                                            |  19%
  |                                                                                                                        
  |===========================                                                                                       |  24%
  |                                                                                                                        
  |==================================                                                                                |  29%
  |                                                                                                                        
  |======================================                                                                            |  33%
  |                                                                                                                        
  |=======================================                                                                           |  34%
  |                                                                                                                        
  |========================================                                                                          |  35%
  |                                                                                                                        
  |=========================================                                                                         |  36%
  |                                                                                                                        
  |==========================================                                                                        |  37%
  |                                                                                                                        
  |===============================================                                                                   |  41%
  |                                                                                                                        
  |===================================================                                                               |  44%
  |                                                                                                                        
  |=======================================================                                                           |  48%
  |                                                                                                                        
  |=========================================================                                                         |  50%
  |                                                                                                                        
  |==========================================================                                                        |  51%
  |                                                                                                                        
  |============================================================                                                      |  52%
  |                                                                                                                        
  |============================================================                                                      |  53%
  |                                                                                                                        
  |===============================================================                                                   |  56%
  |                                                                                                                        
  |====================================================================                                              |  59%
  |                                                                                                                        
  |==========================================================================                                        |  65%
  |                                                                                                                        
  |============================================================================                                      |  67%
  |                                                                                                                        
  |=============================================================================                                     |  68%
  |                                                                                                                        
  |==============================================================================                                    |  68%
  |                                                                                                                        
  |===============================================================================                                   |  69%
  |                                                                                                                        
  |===================================================================================                               |  73%
  |                                                                                                                        
  |=========================================================================================                         |  78%
  |                                                                                                                        
  |===============================================================================================                   |  83%
  |                                                                                                                        
  |================================================================================================                  |  84%
  |                                                                                                                        
  |=====================================================================================================             |  89%
  |                                                                                                                        
  |==========================================================================================================        |  93%
  |                                                                                                                        
  |===============================================================================================================   |  98%
  |                                                                                                                        
  |================================================================================================================= |  99%
  |                                                                                                                        
  |==================================================================================================================| 100%

deallocating memory

rm(d_train, d_test)

Let’s see how it performs. Our goal is to maximize model accuracy but avoiding overfitting at the same time.

Area Under the Curve preparation for plotting ROC for training dataset

dt2 <- data.table(h2o.performance(model, dh2o_train)@metrics$thresholds_and_metric_scores[18:19])
p26 <- ggplot(dt2, aes(x = fpr, y = tpr)) +
          geom_point(color = 'blue',
                     pch = 1,
                     alpha = 0.7) +
          coord_fixed(ratio = 1) +
          xlab('False Positive Rate') +
          ylab('True Positive Rate') +
          ggtitle('Receiver Operating Characteristic of model on traning dataset') +
          theme_igray()

saving ROC for training dataset

file_out = paste(getwd(), '/_tmp/', sep = '')
ggsave(path = file_out, filename = 'p26.png')

plotting ROC for training dataset

plot(p26)

Sys.sleep(2)

Area Under the Curve results You can also see/check/examine the data on the h2o user interface: http://localhost:54321 https://en.wikipedia.org/wiki/Receiver_operating_characteristic#Area_under_the_curve

auc_train <- round(h2o.auc(h2o.performance(model, dh2o_train)), 5)
auc_test <- round(h2o.auc(h2o.performance(model, dh2o_test)), 5)
auc_valid <- round(h2o.auc(h2o.performance(model, dh2o_valid)), 5)
pandoc.header('Area Under the Curve results')

# Area Under the Curve results
pandoc.header('0.90-1.00 = excellent')

# 0.90-1.00 = excellent
pandoc.header('0.80-0.90 = good')

# 0.80-0.90 = good
pandoc.header('0.70-0.80 = fair')

# 0.70-0.80 = fair
pandoc.header('0.60-0.70 = poor')

# 0.60-0.70 = poor
pandoc.header('0.50-0.60 = fail')

# 0.50-0.60 = fail
pandoc.header('')

# 
pandoc.header('Our AUC is excellent for all the train, test and validation dataset. In addition they are very close to each other which means no overfitting!!')

# Our AUC is excellent for all the train, test and validation dataset. In addition they are very close to each other which means no overfitting!!
pandoc.header(paste('training dataset AUC: ', auc_train, sep = ''))

# training dataset AUC: 0.91347
pandoc.header(paste('testing dataset AUC: ', auc_test, sep = ''))

# testing dataset AUC: 0.90053
pandoc.header(paste('validation dataset AUC: ', auc_valid, sep = ''))

# validation dataset AUC: 0.90433

Let see the confusion matrices You can also see/check/examine the data on the h2o user interface: http://localhost:54321 https://en.wikipedia.org/wiki/Confusion_matrix

pandoc.header(' ')

#  
pander('The number of missclassified records are below 3% in each dataset')
The number of missclassified records are below 3% in each dataset
pandoc.header(' ')

#  
pandoc.header('confusion matrix of training dataset')

# confusion matrix of training dataset
pandoc.table(h2o.confusionMatrix(model, dh2o_train))

------------------------------------------------
   &nbsp;     FALSE   TRUE   Error      Rate    
------------ ------- ------ ------- ------------
 **FALSE**   210883   3594  0.01676 =3594/214477

  **TRUE**    1036    1321  0.4395   =1036/2357 

 **Totals**  211919   4915  0.02135 =4630/216834
------------------------------------------------
pandoc.header('confusion matrix of testing dataset')

# confusion matrix of testing dataset
pandoc.table(h2o.confusionMatrix(model, dh2o_test))

------------------------------------------------
   &nbsp;     FALSE   TRUE   Error      Rate    
------------ ------- ------ ------- ------------
 **FALSE**   104862   2309  0.02155 =2309/107171

  **TRUE**     487    759   0.3909   =487/1246  

 **Totals**  105349   3068  0.02579 =2796/108417
------------------------------------------------
pandoc.header('confusion matrix of validation dataset')

# confusion matrix of validation dataset
pandoc.table(h2o.confusionMatrix(model, dh2o_valid))

------------------------------------------------
   &nbsp;     FALSE   TRUE   Error      Rate    
------------ ------- ------ ------- ------------
 **FALSE**   105286   1934  0.01804 =1934/107220

  **TRUE**     510    687   0.4261   =510/1197  

 **Totals**  105796   2621  0.02254 =2444/108417
------------------------------------------------

all metrics of the model You can also see/check/examine the data on the h2o user interface: http://localhost:54321

pandoc.header(' ')

#  
pandoc.header('all metrics of the model for training dataset')

# all metrics of the model for training dataset
print(h2o.performance(model, dh2o_train))
H2OBinomialMetrics: drf

MSE:  0.009096576
RMSE:  0.09537597
LogLoss:  0.03870714
Mean Per-Class Error:  0.2281494
AUC:  0.9134653
Gini:  0.8269305

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:
        FALSE TRUE    Error          Rate
FALSE  210883 3594 0.016757  =3594/214477
TRUE     1036 1321 0.439542    =1036/2357
Totals 211919 4915 0.021353  =4630/216834

Maximum Metrics: Maximum metrics at their respective thresholds
                        metric threshold    value idx
1                       max f1  0.144500 0.363311 123
2                       max f2  0.130193 0.494070 140
3                 max f0point5  0.195928 0.319186  76
4                 max accuracy  0.298412 0.989171  15
5                max precision  0.464822 1.000000   0
6                   max recall  0.001783 1.000000 397
7              max specificity  0.464822 1.000000   0
8             max absolute_mcc  0.136158 0.391695 136
9   max min_per_class_accuracy  0.008103 0.831141 313
10 max mean_per_class_accuracy  0.011004 0.855092 295

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
pandoc.header(' ')

#  
pandoc.header('all metrics of the model for test dataset')

# all metrics of the model for test dataset
print(h2o.performance(model, dh2o_test))
H2OBinomialMetrics: drf

MSE:  0.009727923
RMSE:  0.09863023
LogLoss:  0.04162846
Mean Per-Class Error:  0.2061979
AUC:  0.9005305
Gini:  0.801061

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:
        FALSE TRUE    Error          Rate
FALSE  104862 2309 0.021545  =2309/107171
TRUE      487  759 0.390851     =487/1246
Totals 105349 3068 0.025789  =2796/108417

Maximum Metrics: Maximum metrics at their respective thresholds
                        metric threshold    value idx
1                       max f1  0.138961 0.351878 120
2                       max f2  0.093894 0.482578 152
3                 max f0point5  0.193055 0.295069  65
4                 max accuracy  0.347825 0.988526   1
5                max precision  0.347825 0.750000   1
6                   max recall  0.001859 1.000000 396
7              max specificity  0.432264 0.999991   0
8             max absolute_mcc  0.129148 0.380771 129
9   max min_per_class_accuracy  0.008047 0.815409 309
10 max mean_per_class_accuracy  0.012854 0.846518 284

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`
pandoc.header(' ')

#  
pandoc.header('all metrics of the model for validation dataset')

# all metrics of the model for validation dataset
print(h2o.performance(model, dh2o_valid))
H2OBinomialMetrics: drf

MSE:  0.009277894
RMSE:  0.09632182
LogLoss:  0.03970814
Mean Per-Class Error:  0.2220514
AUC:  0.9043251
Gini:  0.8086503

Confusion Matrix (vertical: actual; across: predicted) for F1-optimal threshold:
        FALSE TRUE    Error          Rate
FALSE  105286 1934 0.018038  =1934/107220
TRUE      510  687 0.426065     =510/1197
Totals 105796 2621 0.022543  =2444/108417

Maximum Metrics: Maximum metrics at their respective thresholds
                        metric threshold    value idx
1                       max f1  0.142376 0.359874 117
2                       max f2  0.136831 0.490710 127
3                 max f0point5  0.194216 0.312965  62
4                 max accuracy  0.352701 0.988969   2
5                max precision  0.352701 0.666667   2
6                   max recall  0.001865 1.000000 396
7              max specificity  0.401867 0.999991   0
8             max absolute_mcc  0.138420 0.390136 125
9   max min_per_class_accuracy  0.008106 0.824561 309
10 max mean_per_class_accuracy  0.040825 0.850574 218

Gains/Lift Table: Extract with `h2o.gainsLift(<model>, <data>)` or `h2o.gainsLift(<model>, valid=<T/F>, xval=<T/F>)`

deallocating memory

rm(p26)

saving model in binary

file_out = paste(getwd(), '/_results/', sep = '')
h2o.saveModel(model, path = file_out)
[1] "/home/sbudai/Documents/projects/GitHub/Client_Acquisition_for_a_Bank/_results/BankiUgyfelModel"

saving model scoring in POJO

file_out = paste(getwd(), '/_results', sep = '')
h2o.download_pojo(model, path = file_out, get_jar = TRUE)
[1] "BankiUgyfelModel.java"

get fitted values of the validation dataset (public_test_scored.csv)

BankiUgyfelModel.fit <- as.data.table(h2o.predict(object = model,
                                                  newdata = dh2o_valid))[, 2:3]

  |                                                                                                                        
  |                                                                                                                  |   0%
  |                                                                                                                        
  |==================================================================================================================| 100%
colnames(BankiUgyfelModel.fit) <- c('p0', 'p1')
thrhd <- h2o.find_threshold_by_max_metric( h2o.performance(model, dh2o_valid), 'f1')
BankiUgyfelModel.fit[, predict := FALSE]
BankiUgyfelModel.fit[p1 >= thrhd, predict := TRUE]
scored_result <- cbind(d_valid, BankiUgyfelModel.fit)
pander(scored_result[sample(.N, 5)], split.table = 200)

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 BankiUgyfel   SajatToke   MerlegEredmeny   AktivTulajdonosSzam   TulajCegesBankiUgyfelNr   Cegkora   LegkozFiokTavProxy   Cegforma   NAVTeruletKod         Megye        
------------- ----------- ---------------- --------------------- ------------------------- --------- -------------------- ---------- --------------- --------------------
    FALSE       3637000        1e+05                 2                       0                 7             59.5            Kft            2              Baranya       

    FALSE       9910000       -1696000               2                       0                19             16.8            Kft           13                Pest        

    FALSE       5828000        828000                1                       0                 1            4.132             Rt           43              Budapest      

    FALSE      -2632000       -515000                2                       0                 3            20.39             Bt           13                Pest        

    FALSE        79000           0                   2                       0                15            132.4             Bt            5        Borsod-Abauj-Zemplen
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------

Table: Table continues below

 
----------------------------------------------------------------------
         FotevekenysegText           AFAKod    p0      p1     predict 
----------------------------------- -------- ------ -------- ---------
         Ingatlanugyletek              1     0.9792 0.02077    FALSE  

    KereskedelemGepjarmujavitas        2     0.9958 0.004166   FALSE  

SzakmaiTudomanyosMuszakiTevekenyseg    2     0.9967 0.00325    FALSE  

             Epitoipar                 1     0.9972 0.002771   FALSE  

             NemIsmert                 3     0.9973 0.002712   FALSE  
----------------------------------------------------------------------

saving predicted result set (based on validation data)

file_out = paste(getwd(), '/_results/scored_result.RData', sep = '')
fwrite(scored_result,
       file = file_out,
       nThread = getDTthreads())

disconnecting from h2o

h2o.shutdown(prompt = FALSE)

deallocating memory!

rm(list = ls())
LS0tCnRpdGxlOiAiQ2xpZW50IEFjcXVpc2l0aW9uIGZvciBhIEJhbmsiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKLS0tCgpUaGlzIHByb2plY3QgaXMgYWJvdXQgdG8gbWluZSBuZXcgc21hbGwgYnVzaW5lc3MgY2xpZW50IGxlYWRzIHRvIGEgQmFuayBiYXNlZCBvbiB0aGUgZmVhdHVyZXMgb2YgYWxyZWFkeSBhY3F1aXJlZCBzbWFsbCBidXNpbmVzcyBjbGllbnRzLgoKIyBJbml0aWFsIEdlbmVyYWwgU2V0dXAKCm5vdGVib29rIHNldHVwCmBgYHtyIHNldHVwLCBpbmNsdWRlPVRSVUUsd2FybmluZz1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KHRpZHk9VFJVRSxvcHRpb25zKHdhcm49MSksY2FjaGU9VFJVRSkKY2hvb3NlQ1JBTm1pcnJvcihncmFwaGljcz1GQUxTRSxpbmQ9MSkKa25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXI9Z2V0d2QoKSkKYGBgCgpjbGVhbmluZyB0aGUgd29ya3BsYWNlCmBgYHtyLCBpbmNsdWRlPVRSVUUsd2FybmluZz1GQUxTRX0Kcm0obGlzdCA9IGxzKCkpCmdjKHJlc2V0ID0gVFJVRSkKdW5saW5rKHBhc3RlKGdldHdkKCksICcvQ2xpZW50X0FjcXVpc2l0aW9uX2Zvcl9hX0JhbmsubmIuaHRtbCcsIHNlcCA9ICcnKSwgcmVjdXJzaXZlID0gVFJVRSkKdW5saW5rKHBhc3RlKGdldHdkKCksICcvX3Jlc3VsdHMvQmFua2lVZ3lmZWxNb2RlbCcsIHNlcCA9ICcnKSwgcmVjdXJzaXZlID0gVFJVRSkKdW5saW5rKHBhc3RlKGdldHdkKCksICcvX3Jlc3VsdHMvQmFua2lVZ3lmZWxNb2RlbC5qYXZhJywgc2VwID0gJycpLCByZWN1cnNpdmUgPSBUUlVFKQp1bmxpbmsocGFzdGUoZ2V0d2QoKSwgJy9fcmVzdWx0cy9oMm8tZ2VubW9kZWwuamFyJywgc2VwID0gJycpLCByZWN1cnNpdmUgPSBUUlVFKQpgYGAKCnNldHRpbmcgZ2VuZXJhbCBvcHRpb25zCmBgYHtyLCBpbmNsdWRlPVRSVUUsd2FybmluZz1GQUxTRX0KU3lzLmdldGxvY2FsZSgpClN5cy5zZXRsb2NhbGUoJ0xDX0FMTCcsJ0MnKSAKb3B0aW9ucyhTdHJpbmdBc0ZhY3RvciA9IEZBTFNFKQpwcmludChwYXN0ZSgnWW91IGFyZSBpbicsIGdldHdkKCksICdkaXJlY3RvcnkuIFRoZSBpbnRlcm1lZGlhdGUgbWF0ZXJpYWxzIGFuZCByZXN1bHRzIHdpbGwgYmUgcGxhY2VkIGhlcmUuJywgc2VwID0gJyAnKSkKYGBgCgpjcmVhdGluZyBuZWNlc3Nhcnkgc3ViZm9sZGVycwpgYGB7ciwgaW5jbHVkZT1UUlVFLHdhcm5pbmc9RkFMU0V9CmRpci5jcmVhdGUoZmlsZS5wYXRoKGdldHdkKCksICdfdG1wJyksIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpkaXIuY3JlYXRlKGZpbGUucGF0aChnZXR3ZCgpLCAnX3Jlc3VsdHMnKSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCmBgYAoKY2FsbGluZyAnaW5zdGFsbC5sb2FkJyBsaWJyYXJ5IGFuZCBpbnN0YWxsaW5nIGlmIHJlcXVpcmVkCkl0IGNvbnRhaW5zIGEgY29vbCBmdW5jdGlvbiBmb3IgcGFja2FnZSBpbnN0YWxsaW5nL2xvYWRpbmcuCmBgYHtyLCBpbmNsdWRlPVRSVUUsd2FybmluZz1GQUxTRX0KaWYoISdpbnN0YWxsLmxvYWQnICVpbiUgcm93bmFtZXMoaW5zdGFsbGVkLnBhY2thZ2VzKCkpKSB7CiAgaW5zdGFsbC5wYWNrYWdlcygnaW5zdGFsbC5sb2FkJykKICBTeXMuc2xlZXAoNikKfQpsaWJyYXJ5KGluc3RhbGwubG9hZCkKYGBgCgpjYWxsaW5nIGFsbCB0aGUgb3RoZXIgbmVjZXNzYXJ5IGxpYnJhcmllcyBhbmQgaW5zdGFsbGluZyBpZiByZXF1aXJlZApgYGB7ciwgaW5jbHVkZT1UUlVFLHdhcm5pbmc9RkFMU0V9Cmluc3RhbGxfbG9hZCgnZm9ybWF0UicsCiAgICAgICAgICAgICAnZGF0YS50YWJsZScsCiAgICAgICAgICAgICAnc3FsZGYnLCAKICAgICAgICAgICAgICdzdHJpbmdpJywgCiAgICAgICAgICAgICAndmFsaWRhdGUnLCAKICAgICAgICAgICAgICdnZ3Bsb3QyJywKICAgICAgICAgICAgICdnZ3RoZW1lcycsCiAgICAgICAgICAgICAnc2ltcHV0YXRpb24nLCAKICAgICAgICAgICAgICdrbml0cicsIAogICAgICAgICAgICAgJ3htbDInLCAKICAgICAgICAgICAgICdyZXNoYXBlMicsCiAgICAgICAgICAgICAnWE1MJywKICAgICAgICAgICAgICdwYW5kZXInLAogICAgICAgICAgICAgJ0ltYXAnLAogICAgICAgICAgICAgJ2lncmFwaCcsCiAgICAgICAgICAgICAnZ2dtYXAnLAogICAgICAgICAgICAgJ3JhbmRvbUZvcmVzdCcsCiAgICAgICAgICAgICAnbWlzc0ZvcmVzdCcsCiAgICAgICAgICAgICAnaDJvJywKICAgICAgICAgICAgICdncmlkJywKICAgICAgICAgICAgICdwbG90bHknLAogICAgICAgICAgICAgJ2h0bWx3aWRnZXRzJywKICAgICAgICAgICAgICdvcGVueGxzeCcsCiAgICAgICAgICAgICAnUlNlbGVuaXVtJykKU3lzLnNsZWVwKDMpCgprZXlzIDwtIGRhdGEudGFibGUoZnJlYWQoJ34vRG9jdW1lbnRzL2NyZWRzLmNzdicpKQprZXlzIDwtIGtleXNbUHJvamVjdFRpdGxlID09ICdDbGllbnRfQWNxdWlzaXRpb25fZm9yX2FfQmFuaycsICgyOjMpLCB3aXRoID0gRkFMU0VdCmBgYAoKY3JlYXRpbmcgZnVuY3Rpb24gdG8gZGlzcGxheSBncmFwaHMgb24gb25lIHBhZ2UKYGBge3IsIGluY2x1ZGU9VFJVRSx3YXJuaW5nPUZBTFNFfQojIGh0dHA6Ly93d3cuY29va2Jvb2stci5jb20vR3JhcGhzL011bHRpcGxlX2dyYXBoc19vbl9vbmVfcGFnZV8oZ2dwbG90MikvCiMjIE11bHRpcGxlIHBsb3QgZnVuY3Rpb24KIyMKIyMgZ2dwbG90IG9iamVjdHMgY2FuIGJlIHBhc3NlZCBpbiAuLi4sIG9yIHRvIHBsb3RsaXN0IChhcyBhIGxpc3Qgb2YgZ2dwbG90IG9iamVjdHMpCiMjIC0gY29sczogICBOdW1iZXIgb2YgY29sdW1ucyBpbiBsYXlvdXQKIyMgLSBsYXlvdXQ6IEEgbWF0cml4IHNwZWNpZnlpbmcgdGhlIGxheW91dC4gSWYgcHJlc2VudCwgJ2NvbHMnIGlzIGlnbm9yZWQuCiMjCiMjIElmIHRoZSBsYXlvdXQgaXMgc29tZXRoaW5nIGxpa2UgbWF0cml4KGMoMSwyLDMsMyksIG5yb3c9MiwgYnlyb3c9VFJVRSksCiMjIHRoZW4gcGxvdCAxIHdpbGwgZ28gaW4gdGhlIHVwcGVyIGxlZnQsIDIgd2lsbCBnbyBpbiB0aGUgdXBwZXIgcmlnaHQsIGFuZAojIyAzIHdpbGwgZ28gYWxsIHRoZSB3YXkgYWNyb3NzIHRoZSBib3R0b20uCiMjIyMKIyAgbXVsdGlwbG90IDwtIGZ1bmN0aW9uKC4uLiwgcGxvdGxpc3QgPSBOVUxMLCBmaWxlLCBjb2xzID0gMSwgbGF5b3V0ID0gTlVMTCkgewojICAjIE1ha2UgYSBsaXN0IGZyb20gdGhlIC4uLiBhcmd1bWVudHMgYW5kIHBsb3RsaXN0CiMgIHBsb3RzIDwtIGMobGlzdCguLi4pLCBwbG90bGlzdCkKIyAgbnVtUGxvdHMgPSBsZW5ndGgocGxvdHMpCiMgICMgSWYgbGF5b3V0IGlzIE5VTEwsIHRoZW4gdXNlICdjb2xzJyB0byBkZXRlcm1pbmUgbGF5b3V0CiMgIGlmIChpcy5udWxsKGxheW91dCkpIHsKIyAgICAjIE1ha2UgdGhlIHBhbmVsCiMgICAgIyBuY29sOiBOdW1iZXIgb2YgY29sdW1ucyBvZiBwbG90cwojICAgICMgbnJvdzogTnVtYmVyIG9mIHJvd3MgbmVlZGVkLCBjYWxjdWxhdGVkIGZyb20gIyBvZiBjb2xzCiMgICAgbGF5b3V0IDwtIG1hdHJpeChzZXEoMSwgY29scyAqIGNlaWxpbmcobnVtUGxvdHMvY29scykpLAojICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IGNvbHMsIAojICAgICAgICAgICAgICAgICAgICAgbnJvdyA9IGNlaWxpbmcobnVtUGxvdHMvY29scykpCiMgIH0KIyAgaWYgKG51bVBsb3RzID09IDEpIHsKIyAgICBwcmludChwbG90c1tbMV1dKQojICAgIH0gZWxzZSB7CiMgICAgIyBTZXQgdXAgdGhlIHBhZ2UKIyAgICBncmlkLm5ld3BhZ2UoKQojICAgIHB1c2hWaWV3cG9ydCh2aWV3cG9ydChsYXlvdXQgPSBncmlkLmxheW91dChucm93KGxheW91dCksIG5jb2wobGF5b3V0KSkpKQojICAgICMgTWFrZSBlYWNoIHBsb3QsIGluIHRoZSBjb3JyZWN0IGxvY2F0aW9uCiMgICAgZm9yIChpIGluIDE6bnVtUGxvdHMpIHsKIyAgICAgICMgR2V0IHRoZSBpLGogbWF0cml4IHBvc2l0aW9ucyBvZiB0aGUgcmVnaW9ucyB0aGF0IGNvbnRhaW4gdGhpcyBzdWJwbG90CiMgICAgICBtYXRjaGlkeCA8LSBhcy5kYXRhLnRhYmxlKHdoaWNoKGxheW91dCA9PSBpLCBhcnIuaW5kID0gVFJVRSkpCiMgICAgICBwcmludChwbG90c1tbaV1dLCB2cCA9IHZpZXdwb3J0KGxheW91dC5wb3Mucm93ID0gbWF0Y2hpZHhbLCByb3ddLAojICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXlvdXQucG9zLmNvbCA9IG1hdGNoaWR4WywgY29sXSkpCiMgICAgfQojICB9CiN9CmBgYAoKCiMgU291cmNlIERhdGEgSW1wb3J0CgppbXBvcnRpbmcgYWxsIGNvbXBhbmllcycgbGlzdApgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmaWxlX2luID0gcGFzdGUoJ2h0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvJywga2V5c1tBcHBOYW1lID09ICdmZWx2ZXRlbGlfZmVsYWRhdF92MicsIDJdLCAnP3Jhdz0xJywgc2VwID0gJycpCmNvbXBhbnlfbGlzdCA8LSBmcmVhZChmaWxlX2luLCAKICAgICAgICAgICAgICAgICAgICAgIHNlcCA9ICc7JywgCiAgICAgICAgICAgICAgICAgICAgICBuYS5zdHJpbmdzID0gYygnbmEnLCAnbi9hJywgJ05BJywgJ04vQScsICcnKSwgCiAgICAgICAgICAgICAgICAgICAgICBzdHJpcC53aGl0ZSA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgaW50ZWdlcjY0ID0gJ251bWVyaWMnLAogICAgICAgICAgICAgICAgICAgICAgY29sQ2xhc3NlcyA9IGxpc3QoY2hhcmFjdGVyID0gMSwgNSwgNiwgNywgMTIsIDE0KSkKYGBgCgpsZXQncyBicmluZyBjb2x1bW4gbmFtZXMgaW50byBhIGdlbmVyYWwgbmFtZSBjb252ZW50aW9uCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNvbG5hbWVzKGNvbXBhbnlfbGlzdCkgPC0gZ3N1YignICcsICcnLCBzdHJpX3RyYW5zX3RvdGl0bGUoZ3N1YignXycsICcgJywgc3RyaV90cmFuc19nZW5lcmFsKGNvbG5hbWVzKGNvbXBhbnlfbGlzdCksICdMYXRpbi1BU0NJSScpKSkpCgpjb21wYW55X2xpc3RbLCAnOj0nIChGb3RldmVrZW55c2VnVGV4dCA9IHN0cmlfdHJhbnNfZ2VuZXJhbChGb3RldmVrZW55c2VnVGV4dCAsICdMYXRpbi1BU0NJSScpLAogICAgICAgICAgICAgICAgICAgICBTemVraGVseSA9IHN0cmlfdHJhbnNfZ2VuZXJhbChTemVraGVseSwgJ0xhdGluLUFTQ0lJJyksCiAgICAgICAgICAgICAgICAgICAgIFN6ZWtoZWx5VmFyb3MgPSBzdHJpX3RyYW5zX2dlbmVyYWwoU3pla2hlbHlWYXJvcywgJ0xhdGluLUFTQ0lJJykpXQpgYGAKCnNhdmluZwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvX3RtcC9jb21wYW55X2xpc3QuUkRhdGEnLCBzZXAgPSAnJykKZndyaXRlKGNvbXBhbnlfbGlzdCwgCiAgICAgICBmaWxlID0gZmlsZV9vdXQsCiAgICAgICBuVGhyZWFkID0gZ2V0RFR0aHJlYWRzKCkpCmBgYAoKbGV0J3MgY2hlY2sgYSBzYW1wbGUgb2YgdGhlIGRhdGEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcGFuZGVyKGNvbXBhbnlfbGlzdFtzYW1wbGUoLk4sIDUpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKaW1wb3J0aW5nIG93bmVyIGNvbXBhbmllcycgbGlzdApgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmaWxlX2luID0gcGFzdGUoJ2h0dHBzOi8vd3d3LmRyb3Bib3guY29tL3MvJywga2V5c1tBcHBOYW1lID09ICdmZWx2ZXRlbGlfZmVsYWRhdF90dWxham9rX2xpc3RhamEnLCAyXSwgJz9yYXc9MScsIHNlcCA9ICcnKQpvd25lcl9jb21wYW55X2xpc3QgPC0gZnJlYWQoZmlsZV9pbiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAnOycsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmEuc3RyaW5ncyA9IGMoJ25hJywgJ24vYScsICdOQScsICdOL0EnLCAnJyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaXAud2hpdGUgPSBUUlVFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbENsYXNzZXMgPSBsaXN0KGNoYXJhY3RlciA9IDEsIDIpKQpgYGAKIApsZXQncyBicmluZyBjb2x1bW4gbmFtZXMgaW50byBhIGdlbmVyYWwgbmFtZSBjb252ZW50aW9uCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNvbG5hbWVzKG93bmVyX2NvbXBhbnlfbGlzdCkgPC0gZ3N1YignICcsICcnLCBzdHJpX3RyYW5zX3RvdGl0bGUoZ3N1YignXycsICcgJywgc3RyaV90cmFuc19nZW5lcmFsKGNvbG5hbWVzKG93bmVyX2NvbXBhbnlfbGlzdCksICdMYXRpbi1BU0NJSScpKSkpCmBgYAoKc2F2aW5nCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9fdG1wL293bmVyX2NvbXBhbnlfbGlzdC5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUob3duZXJfY29tcGFueV9saXN0LCAKICAgICAgIGZpbGUgPSBmaWxlX291dCwKICAgICAgIG5UaHJlYWQgPSBnZXREVHRocmVhZHMoKSkKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIob3duZXJfY29tcGFueV9saXN0W3NhbXBsZSguTiwgNSldLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgppbXBvcnRpbmcgcG9zdGNvZGUgbGlzdCBvZiBiYW5rIGJyYW5jaGVzCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfaW4gPSBwYXN0ZSgnaHR0cHM6Ly93d3cuZHJvcGJveC5jb20vcy8nLCBrZXlzW0FwcE5hbWUgPT0gJ2lyc3phbWxpc3RhJywgMl0sICc/cmF3PTEnLCBzZXAgPSAnJykKYnJhbmNoX3ppcF9saXN0IDwtIGZyZWFkKGZpbGVfaW4sCiAgICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAnOycsIAogICAgICAgICAgICAgICAgICAgICAgICAgbmEuc3RyaW5ncyA9IGMoJ25hJywgJ24vYScsICdOQScsICdOL0EnLCAnJyksIAogICAgICAgICAgICAgICAgICAgICAgICAgc3RyaXAud2hpdGUgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgY29sQ2xhc3NlcyA9IGxpc3QoY2hhcmFjdGVyID0gMSkpCnNldG5hbWVzKGJyYW5jaF96aXBfbGlzdCwgJ2lyw6FuecOtdMOzc3rDoW0nLCAnSXJTemFtJykKYGBgCgpzYXZpbmcKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL190bXAvYnJhbmNoX3ppcF9saXN0LlJEYXRhJywgc2VwID0gJycpCmZ3cml0ZShicmFuY2hfemlwX2xpc3QsIAogICAgICAgZmlsZSA9IGZpbGVfb3V0LAogICAgICAgblRocmVhZCA9IGdldERUdGhyZWFkcygpKQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihicmFuY2hfemlwX2xpc3Rbc2FtcGxlKC5OLCA1KV0sIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCiMgRGF0YSBFeHBsb3JhdGlvbiAmIENsZWFuaW5nIAoKaW5pdGlhbCBzdW1tYXJ5IG9mIGNvbXBhbnkgbGlzdCAtIGxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihzdW1tYXJ5KGNvbXBhbnlfbGlzdCksIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCnByZXJlcXVpc2l0ZSBvZiBkZXRlY3Rpb24gb2YgQWRvc3phbSB2YWx1ZXMgYmVpbmcgdW5pcXVlIGlzIHRvIG9yZGVyIHRoZW0gYnkgdGhhdCBjb2x1bW4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY29tcGFueV9saXN0IDwtIGNvbXBhbnlfbGlzdFtvcmRlcihBZG9zemFtKV0KYGBgCgpsZXQncyBjaGVjayBjb2x1bW4gdmFsdWVzIHZhbGlkaXR5CmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmN0IDwtIGNoZWNrX3RoYXQoY29tcGFueV9saXN0LCAKICAgICAgICAgICAgICAgICB2YWxpZF9ub3RhdGlvbl9CYW5raVVneWZlbCA9IEJhbmtpVWd5ZmVsID09IDAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IEJhbmtpVWd5ZmVsID09IDEsCiAgICAgICAgICAgICAgICAgZGlzdGluY3RfQWRvc3phbSA9IHJsZShzb3J0KEFkb3N6YW0pKVtbMV1dID09IDEsCiAgICAgICAgICAgICAgICAgY29tcGxldGVfQWRvc3phbSA9IHN0cmlfbGVuZ3RoKEFkb3N6YW0pID09IDgsCiAgICAgICAgICAgICAgICAgY29tcGxldGVfQWRvc3phbUhvc3N6dSA9IHN0cmlfbGVuZ3RoKEFkb3N6YW1Ib3NzenUpID09IDEzLAogICAgICAgICAgICAgICAgIGlkZW50aWNhbF9BZG9zemFtX2FuZF9BZG9zemFtSG9zc3p1ID0gQWRvc3phbSA9PSBzdWJzdHIoQWRvc3phbUhvc3N6dSwxICwgOCksCiAgICAgICAgICAgICAgICAgaW5pdGNhcF9DZWdmb3JtYSA9IENlZ2Zvcm1hID09IHN0cmlfdHJhbnNfdG90aXRsZShDZWdmb3JtYSksCiAgICAgICAgICAgICAgICAgdmFsaWRfQWt0aXZUdWxhamRvbm9zU3phbSA9IEFrdGl2VHVsYWpkb25vc1N6YW0gPj0gMSwKICAgICAgICAgICAgICAgICB2YWxpZF9Ba3RpdlByaXZhdFR1bGFqZG9ub3MgPSBBa3RpdlR1bGFqZG9ub3NTemFtID49IEFrdGl2UHJpdmF0VHVsYWpkb25vcywKICAgICAgICAgICAgICAgICBmaWxsZWRfQWt0aXZUdWxhamRvbm9zU3phbV9hbmRfQWt0aXZQcml2YXRUdWxhamRvbm9zID0gc3RyaV9sZW5ndGgoQWt0aXZUdWxhamRvbm9zU3phbSkgPj0gMQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiBzdHJpX2xlbmd0aChBa3RpdlByaXZhdFR1bGFqZG9ub3MpID49IDEsCiAgICAgICAgICAgICAgICAgbm90X25lZ2F0aXZlX0NlZ2tvcmEgPSBDZWdrb3JhID49IDAsCiAgICAgICAgICAgICAgICAgdW5kZXJfVUNMX0NlZ2tvcmEgPSBDZWdrb3JhIDw9IG1lYW4oQ2Vna29yYSwgbmEucm0gPSBUUlVFKSArIDMgKiBzZChDZWdrb3JhLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgICAgICAgIHZhbGlkX0xldHN6YW0gPSBMZXRzemFtID49IDEsCiAgICAgICAgICAgICAgICAgZmlsbGVkX0ZvdGV2ZWtlbnlzZWdUZXh0ID0gc3RyaV9sZW5ndGgoRm90ZXZla2VueXNlZ1RleHQpID49IDEsCiAgICAgICAgICAgICAgICAgaW5pdGNhcF9Gb3RldmVrZW55c2VnVGV4dCA9IEZvdGV2ZWtlbnlzZWdUZXh0ID09IHN0cmlfdHJhbnNfdG90aXRsZShGb3RldmVrZW55c2VnVGV4dCksCiAgICAgICAgICAgICAgICAgdmFsaWRfcG9zdGNvZGUgPSBuY2hhcihzdWIoJyAuKicsICcnLCBzdHJpX3RyaW0oU3pla2hlbHkpKSkgPT0gNCwKICAgICAgICAgICAgICAgICB2YWxpZF9TemVraGVseVZhcm9zID0gc3RyaV9sZW5ndGgoU3pla2hlbHlWYXJvcykgPj0gMgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiBncmVwbCgnXFxkJywgU3pla2hlbHlWYXJvcykgPT0gRkFMU0UsCiAgICAgICAgICAgICAgICAgaW5pdGNhcF9TemVraGVseVZhcm9zID0gU3pla2hlbHlWYXJvcyA9PSBzdHJpX3RyYW5zX3RvdGl0bGUoU3pla2hlbHlWYXJvcyksIAogICAgICAgICAgICAgICAgIHZhbGlkX1N6ZWtoZWx5ID0gc3RyaV9sZW5ndGgoU3pla2hlbHkpID49IDExCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiBncmVwbCgnXFxEJywgc3Vic3RyKHN0cmlfdHJpbShTemVraGVseSksIDEsIDQpKSA9PSBGQUxTRQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgZ3JlcGwoJ1xcXicsIFN6ZWtoZWx5KSA9PSBGQUxTRSwKICAgICAgICAgICAgICAgICBpbml0Y2FwX1N6ZWtoZWx5ID0gU3pla2hlbHkgPT0gc3RyaV90cmFuc190b3RpdGxlKFN6ZWtoZWx5KSwgCiAgICAgICAgICAgICAgICAgZmlsbGVkX0FyYmV2ZXRlbCA9IHN0cmlfbGVuZ3RoKEFyYmV2ZXRlbCkgPj0gMSwKICAgICAgICAgICAgICAgICBmaWxsZWRfTWVybGVnRXJlZG1lbnkgPSBzdHJpX2xlbmd0aChNZXJsZWdFcmVkbWVueSkgPj0gMSwKICAgICAgICAgICAgICAgICBmaWxsZWRfU2FqYXRUb2tlID0gc3RyaV9sZW5ndGgoU2FqYXRUb2tlKSA+PSAxLAogICAgICAgICAgICAgICAgIGZpbGxlZF9Gb3RldmVrZW55c2VnVGV4dF9hbmRfQWt0aXZUdWxhamRvbm9zU3phbV9hbmRfQ2VnZm9ybWEgPSBzdHJpX2xlbmd0aChGb3RldmVrZW55c2VnVGV4dCkgPj0gMQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiBzdHJpX2xlbmd0aChBa3RpdlR1bGFqZG9ub3NTemFtKSA+PSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIHN0cmlfbGVuZ3RoKENlZ2Zvcm1hKSA+PSAxCiAgICAgICAgICAgICAgICAgKQpkdCA8LSBkYXRhLnRhYmxlKHN1bW1hcnkoY3QpKQpkdCA8LSBkdFssIGMoMTo1LCA4KV0KYGBgCgpsZXQncyBzZWUgdGhlIHZhbGlkaXR5IG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihkdCwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKQWRvc3phbSBpcyBub3QgZGlzdGluY3QgaW4gdGhlIHRhYmxlLCBzbyBJIGRyb3AgdGhlIHJlcGVhdGluZyBsaW5lcyBhbmQgZG91YmxlIGNoZWNrIGl0CmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdCA8LSBkYXRhLnRhYmxlKHVuaXF1ZShjb21wYW55X2xpc3QpKQpgYGAKCnByZXJlcXVpc2l0ZSBvZiBkZXRlY3Rpb24gb2YgQWRvc3phbSB2YWx1ZXMgYmVpbmcgdW5pcXVlIGlzIHRvIG9yZGVyIHRoZW0gYnkgdGhhdCBjb2x1bW4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY29tcGFueV9saXN0IDwtIGNvbXBhbnlfbGlzdFtvcmRlcihBZG9zemFtKV0KY3QgPC0gY2hlY2tfdGhhdChjb21wYW55X2xpc3QsCiAgICAgICAgICAgICAgICAgZGlzdGluY3RfQWRvc3phbSA9IHJsZShzb3J0KEFkb3N6YW0pKVtbMV1dID09IDEpCmR0IDwtIGRhdGEudGFibGUoc3VtbWFyeShjdCkpCmR0IDwtIGR0WywgYygxOjUsIDgpXQpgYGAKCmxldCdzIHNlZSB0aGUgdmFsaWRpdHkgb2YgdGhlIGRhdGEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcGFuZGVyKGR0LCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCkl0IHNlZW1zIG9rLiBXZSBnb3QgcmlkIG9mIGR1cGxpY2F0ZXMuCgoKbGV0J3MgZHJvcCBjb21tYXMgZnJvbSBTemVraGVseSBhbmQgU3pla2hlbHlWYXJvcyBjb2x1bW5zICYgY2hlY2sgYSBzYW1wbGUgb2YgdGhlIGRhdGEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcGFuZGVyKGNvbXBhbnlfbGlzdFtncmVwbCgnLCcsIFN6ZWtoZWx5KSB8IGdyZXBsKCcsJywgU3pla2hlbHlWYXJvcyksIC4oQWRvc3phbSwgU3pla2hlbHksICBTemVraGVseVZhcm9zKV0pCmNvbXBhbnlfbGlzdFtncmVwbCgnLCcsIFN6ZWtoZWx5KSwgU3pla2hlbHkgOj0gZ3N1YignLCcsICcnLCBTemVraGVseSldCmNvbXBhbnlfbGlzdFtncmVwbCgnLCcsIFN6ZWtoZWx5VmFyb3MpLCBTemVraGVseVZhcm9zIDo9IGdzdWIoJywnLCAnJywgU3pla2hlbHlWYXJvcyldCmBgYAoKbGV0J3MgdHJhbnNmb3JtIHRleHQgY29sdW1ucyB0byBpbml0aWFsIGNhcGl0YWxzCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdFtDZWdmb3JtYSAhPSBzdHJpX3RyYW5zX3RvdGl0bGUoQ2VnZm9ybWEpLCBDZWdmb3JtYSA6PSBzdHJpX3RyYW5zX3RvdGl0bGUoQ2VnZm9ybWEpXQpjb21wYW55X2xpc3RbRm90ZXZla2VueXNlZ1RleHQgIT0gc3RyaV90cmFuc190b3RpdGxlKEZvdGV2ZWtlbnlzZWdUZXh0KSwgRm90ZXZla2VueXNlZ1RleHQgOj0gc3RyaV90cmFuc190b3RpdGxlKEZvdGV2ZWtlbnlzZWdUZXh0KV0KY29tcGFueV9saXN0W1N6ZWtoZWx5VmFyb3MgIT0gc3RyaV90cmFuc190b3RpdGxlKFN6ZWtoZWx5VmFyb3MpLCBTemVraGVseVZhcm9zIDo9IHN0cmlfdHJhbnNfdG90aXRsZShTemVraGVseVZhcm9zKV0KY29tcGFueV9saXN0W1N6ZWtoZWx5ICE9IHN0cmlfdHJhbnNfdG90aXRsZShTemVraGVseSksIFN6ZWtoZWx5IDo9IHN0cmlfdHJhbnNfdG90aXRsZShTemVraGVseSldCmBgYAoKbGV0J3MgdHJhbnNmb3JtIHRoZSBkZXBlbmRlbnQgdmFyaWFibGUgKEJhbmtpVWd5ZmVsKSBpbnRvIGEgcmVhbCBkdW1teSB2YXJpYWJsZQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpjb21wYW55X2xpc3RbQmFua2lVZ3lmZWwgPT0gMSwgQmFua2lVZ3lmZWxfIDo9IFRSVUVdCmNvbXBhbnlfbGlzdFtpcy5uYShCYW5raVVneWZlbCksIEJhbmtpVWd5ZmVsXyA6PSBGQUxTRV0KY29tcGFueV9saXN0WywgQmFua2lVZ3lmZWwgOj0gTlVMTF0Kc2V0bmFtZXMoY29tcGFueV9saXN0LCAnQmFua2lVZ3lmZWxfJywgJ0JhbmtpVWd5ZmVsJykKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIoY29tcGFueV9saXN0WywgLk4sIGJ5ID0gQmFua2lVZ3lmZWxdLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgp0aGVyZSBhcmUgc29tZSBjb21wYW5pZXMgd2l0aCBub3QgbWF0Y2hpbmcgQWRvc3phbSBhbmQgQWRvc3phbUhvc3N6dSAvIEkgZHJvcCB0aG9zZSBBZG9zemFtSG9zc3p1IGRhdGEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KbiA8LSBjb21wYW55X2xpc3RbQWRvc3phbSAhPSBzdWJzdHIoQWRvc3phbUhvc3N6dSwxICwgOCksIC5OXQpwYW5kZXIocGFzdGUoJ1RoZSBudW1iZXIgb2Ygbm90IG1hdGNoaW5nIEFkb3N6YW1Ib3NzenUgZGF0YTogJywgbiwgc2VwID0gJycpKQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihjb21wYW55X2xpc3RbQWRvc3phbSAhPSBzdWJzdHIoQWRvc3phbUhvc3N6dSwxICwgOCksIC4oQWRvc3phbSwgQWRvc3phbUhvc3N6dSwgU3pla2hlbHkpXVtzYW1wbGUoLk4sIDUpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKZHJvcHBpbmcgbm90IG1hdGNoaW5nIEFkb3N6YW1Ib3NzenUgdmFsdWVzCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdFtBZG9zemFtICE9IHN1YnN0cihBZG9zemFtSG9zc3p1LDEgLCA4KSwgQWRvc3phbUhvc3N6dSA6PSBOQV0KYGBgCgp0aGVyZSBhcmUgc29tZSBjb21wYW5pZXMgd2l0aCBub3QgY29tcGxldGUgQWRvc3phbUhvc3N6dSAvIEkgZHJvcCB0aG9zZSBBZG9zemFtSG9zc3p1IHZhbHVlcwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpuIDwtIGNvbXBhbnlfbGlzdFtzdHJpX2xlbmd0aChBZG9zemFtSG9zc3p1KSA8IDEzLCAuTl0KcGFuZGVyKHBhc3RlKCdUaGUgbnVtYmVyIG9mIG5vdCBjb21wbGV0ZSBBZG9zemFtSG9zc3p1IGRhdGE6ICcsIG4sIHNlcCA9ICcnKSkKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIoY29tcGFueV9saXN0W3N0cmlfbGVuZ3RoKEFkb3N6YW1Ib3NzenUpIDwgMTMsIC4oQWRvc3phbSwgQWRvc3phbUhvc3N6dSwgU3pla2hlbHkpXVtzYW1wbGUoLk4sIDUpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKZHJvcHBpbmcgbm9uc2Vuc2UgQWRvc3phbUhvc3N6dSB2YWx1ZXMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY29tcGFueV9saXN0W3N0cmlfbGVuZ3RoKEFkb3N6YW1Ib3NzenUpIDwgMTMsIEFkb3N6YW1Ib3NzenUgOj0gTkFdCmBgYAoKdGhlcmUgYXJlIHNvbWUgcHJvYmFibGUgb3V0bGllcnMgaW4gdGVybXMgb2YgQ2VnS29yYSAvIEkgaGF2ZSBkb3VibGUgY2hlY2tlZCwgdGhlcmUgY291bGQgYmUgY29tcGFuaWVzIHdpdGggdGhpcyBhZ2Ugb3V0IHRoZXJlCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmR0IDwtIGNvbXBhbnlfbGlzdFtDZWdrb3JhID4gbWVhbihDZWdrb3JhLCBuYS5ybSA9IFRSVUUpICsgMyAqIHNkKENlZ2tvcmEsIG5hLnJtID0gVFJVRSksIC5OLCBieSA9IENlZ2tvcmFdCmR0W29yZGVyKC1DZWdrb3JhKV0KYGBgCgp0aGVyZSBhcmUgc29tZSBGb3RldmVrZW55c2VnVGV4dCB2YWx1ZXMgYXJlIG1pc3NpbmcgLyBJIHdpbGwgZmlsbCB0aGVtIHVwIHdpdGggYXMgTmVtSXNtZXJ0CiYgY29sbGFwc2UgdmFsdWUgc3RyaW5ncwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpuIDwtIGNvbXBhbnlfbGlzdFtpcy5uYShGb3RldmVrZW55c2VnVGV4dCksIC5OXQpwYW5kZXIocGFzdGUoJ1RoZSBudW1iZXIgb2YgbWlzc2luZyBGb3RldmVrZW55c2VnVGV4dCB2YWx1ZXM6ICcsIG4sIHNlcCA9ICcnKSkKY29tcGFueV9saXN0W2lzLm5hKEZvdGV2ZWtlbnlzZWdUZXh0KSwgRm90ZXZla2VueXNlZ1RleHQgOj0gJ05lbUlzbWVydCddCmNvbXBhbnlfbGlzdFtncmVwbCgnLCcsIEZvdGV2ZWtlbnlzZWdUZXh0KSwgRm90ZXZla2VueXNlZ1RleHQgOj0gZ3N1YignLCcsICcnLCBGb3RldmVrZW55c2VnVGV4dCldCmNvbXBhbnlfbGlzdFtncmVwbCgnICcsIEZvdGV2ZWtlbnlzZWdUZXh0KSwgRm90ZXZla2VueXNlZ1RleHQgOj0gZ3N1YignICcsICcnLCBGb3RldmVrZW55c2VnVGV4dCldCmR0IDwtIGRhdGEudGFibGUoY29tcGFueV9saXN0WywgLk4sIGJ5ID0gRm90ZXZla2VueXNlZ1RleHRdKQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihkdFtvcmRlcigtTiwgRm90ZXZla2VueXNlZ1RleHQpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKdGhlcmUgYXJlIHNvbWUgbm90IHZhbGlkIFN6ZWtoZWx5IHZhbHVlcyAvIEkgZHJvcCB0aG9zZSBTemVraGVseSB2YWx1ZXMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KbiA8LSBjb21wYW55X2xpc3Rbc3RyaV9sZW5ndGgoU3pla2hlbHkpIDwgMTEKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgZ3JlcGwoJ1xcRCcsIHN1YnN0cihzdHJpX3RyaW0oU3pla2hlbHkpLCAxLCA0KSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgZ3JlcGwoJ1xcXicsIFN6ZWtoZWx5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5OXQpwYW5kZXIocGFzdGUoJ1RoZSBudW1iZXIgb2Ygbm90IHZhbGlkIFN6ZWtoZWx5IHZhbHVlczogJywgbiwgc2VwID0gJycpKQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihjb21wYW55X2xpc3Rbc3RyaV9sZW5ndGgoU3pla2hlbHkpIDwgMTEKICAgICAgICAgICAgICAgICAgfCBncmVwbCgnXFxEJywgc3Vic3RyKHN0cmlfdHJpbShTemVraGVseSksIDEsIDQpKQogICAgICAgICAgICAgICAgICB8IGdyZXBsKCdcXF4nLCBTemVraGVseSkKICAgICAgICAgICAgICAgICAgLCAuKEFkb3N6YW0sIFN6ZWtoZWx5LCBTemVraGVseVZhcm9zKV0sIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCmRyb3BwaW5nIG5vbnNlbnNlIFN6ZWtoZWx5IHZhbHVlcwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpjb21wYW55X2xpc3Rbc3RyaV9sZW5ndGgoU3pla2hlbHkpIDwgMTEKICAgICAgICAgICAgfCBncmVwbCgnXFxEJywgc3Vic3RyKHN0cmlfdHJpbShTemVraGVseSksIDEsIDQpKQogICAgICAgICAgICB8IGdyZXBsKCdcXF4nLCBTemVraGVseSkgICAgICAgICAgICAgCiAgICAgICAgICAgICwgU3pla2hlbHkgOj0gTkFdCmBgYAoKdGhlcmUgYXJlIHNvbWUgbm90IHZhbGlkIFN6ZWtoZWx5VmFyb3MgdmFsdWVzIC8gSSBkcm9wIHRob3NlIFN6ZWtoZWx5VmFyb3MgdmFsdWVzCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9Cm4gPC0gY29tcGFueV9saXN0W3N0cmlfbGVuZ3RoKHN0cmlfZW5jX3RvdXRmOChTemVraGVseVZhcm9zLCB2YWxpZGF0ZSA9IFRSVUUpKSA8IDIKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgZ3JlcGwoJ1xcZCcsIHN0cmlfZW5jX3RvdXRmOChTemVraGVseVZhcm9zLCB2YWxpZGF0ZSA9IFRSVUUpKSwgLk5dCnBhbmRlcihwYXN0ZSgnVGhlIG51bWJlciBvZiBub3QgdmFsaWQgU3pla2hlbHlWYXJvcyB2YWx1ZXM6ICcsIG4sIHNlcCA9ICcnKSkKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIoY29tcGFueV9saXN0W3N0cmlfbGVuZ3RoKHN0cmlfZW5jX3RvdXRmOChTemVraGVseVZhcm9zLCB2YWxpZGF0ZSA9IFRSVUUpKSA8IDIKICAgICAgICAgICAgICAgICAgIHwgZ3JlcGwoJ1xcZCcsIHN0cmlfZW5jX3RvdXRmOChTemVraGVseVZhcm9zLCB2YWxpZGF0ZSA9IFRSVUUpKSAKICAgICAgICAgICAgICAgICAgICwgLihBZG9zemFtLCBTemVraGVseSwgc3RyaV9lbmNfdG91dGY4KFN6ZWtoZWx5VmFyb3MsIHZhbGlkYXRlID0gVFJVRSkpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKZHJvcHBpbmcgbm9uc2Vuc2UgU3pla2hlbHlWYXJvcyB2YWx1ZXMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY29tcGFueV9saXN0W3N0cmlfbGVuZ3RoKHN0cmlfZW5jX3RvdXRmOChTemVraGVseVZhcm9zLCB2YWxpZGF0ZSA9IFRSVUUpKSA8IDIKICAgICAgICAgICB8IGdyZXBsKCdcXGQnLCBzdHJpX2VuY190b3V0ZjgoU3pla2hlbHlWYXJvcywgdmFsaWRhdGUgPSBUUlVFKSkgCiAgICAgICAgICAgLCBTemVraGVseVZhcm9zIDo9IE5BXQpgYGAKCnNhdmluZyBtb2RpZmllZCBjb21wYW55X2xpc3QgaW4gUkRhdGEgZm9ybWF0CmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9fdG1wL2NvbXBhbnlfbGlzdC5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUoY29tcGFueV9saXN0LCAKICAgICAgIGZpbGUgPSBmaWxlX291dCwKICAgICAgIG5UaHJlYWQgPSBnZXREVHRocmVhZHMoKSkKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIoY29tcGFueV9saXN0W3NhbXBsZSguTiwgNSldLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgaGlzdG9ncmFtcyBvZiBjb2x1bW5zICh2YXJpYWJsZXMpIG9mIGNvbXBhbnkgbGlzdApgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwMSA8LSBnZ3Bsb3QoY29tcGFueV9saXN0KSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gU2FqYXRUb2tlKSwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gJ2JsdWUnLCAKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgICAgICAgYmlud2lkdGggPSAwLjMsCiAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsgCiAgICAgICAgZ2d0aXRsZSgnSGlzdG9ncmFtIGZvciBsb2cxMF9TYWphdFRva2UnKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIHhsYWIoJ2xvZzEwX1NhamF0VG9rZScpICsKICAgICAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpICAKCnAyIDwtIGdncGxvdChjb21wYW55X2xpc3QpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBNZXJsZWdFcmVkbWVueSksCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICdncmVlbicsIAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gJ2lkZW50aXR5JywKICAgICAgICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDAuMywKICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IEkoMC43KSkgKyAKICAgICAgICBnZ3RpdGxlKCdIaXN0b2dyYW0gZm9yIGxvZzEwX01lcmxlZ0VyZWRtZW55JykgKwogICAgICAgIGZhY2V0X2dyaWQoQmFua2lVZ3lmZWwgfiAuLCBzY2FsZXMgPSAnZnJlZScsIGxhYmVsbGVyID0gbGFiZWxsZXIoQmFua2lVZ3lmZWwgPSBjKCdUUlVFJyA9ICdCYW5raSBVZ3lmZWwnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRkFMU0UnID0gJ05vdCBCYW5raSBVZ3lmZWwnKSkpICsKICAgICAgICB4bGFiKCdsb2cxMF9NZXJsZWdFcmVkbWVueScpICsKICAgICAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpICAKCnAzIDwtIGdncGxvdChjb21wYW55X2xpc3QpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBBcmJldmV0ZWwpLAogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAnb3JhbmdlJywgCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4zLAogICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArIAogICAgICAgIGdndGl0bGUoJ0hpc3RvZ3JhbSBmb3IgbG9nMTBfQXJiZXZldGVsJykgKwogICAgICAgIGZhY2V0X2dyaWQoQmFua2lVZ3lmZWwgfiAuLCBzY2FsZXMgPSAnZnJlZScsIGxhYmVsbGVyID0gbGFiZWxsZXIoQmFua2lVZ3lmZWwgPSBjKCdUUlVFJyA9ICdCYW5raSBVZ3lmZWwnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRkFMU0UnID0gJ05vdCBCYW5raSBVZ3lmZWwnKSkpICsKICAgICAgICB4bGFiKCdsb2cxMF9BcmJldmV0ZWwnKSArCiAgICAgICAgc2NhbGVfeF9sb2cxMCgpICsKICAgICAgICB0aGVtZV9pZ3JheSgpClN5cy5zbGVlcCgyKQoKcDQgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdCkgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IEFrdGl2UHJpdmF0VHVsYWpkb25vcyksCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICdyZWQnLCAKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgICAgICAgYmlud2lkdGggPSAwLjEsCiAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsgCiAgICAgICAgZ2d0aXRsZSgnSGlzdG9ncmFtIGZvciBsb2cxMF9Ba3RpdlByaXZhdFR1bGFqZG9ub3MnKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIHhsYWIoJ2xvZzEwX0FrdGl2UHJpdmF0VHVsYWpkb25vcycpICsKICAgICAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwNSA8LSBnZ3Bsb3QoY29tcGFueV9saXN0KSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gQWt0aXZUdWxhamRvbm9zU3phbSksCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICdsaWdodGdyZWVuJywgCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4xLAogICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArIAogICAgICAgIGdndGl0bGUoJ0hpc3RvZ3JhbSBmb3IgbG9nMTBfQWt0aXZUdWxhamRvbm9zU3phbScpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgeGxhYignbG9nMTBfQWt0aXZUdWxhamRvbm9zU3phbScpICsKICAgICAgICBzY2FsZV94X2xvZzEwKCkgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwNiA8LSBnZ3Bsb3QoY29tcGFueV9saXN0KSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gQ2Vna29yYSksCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICdicm93bicsCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4yLAogICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArCiAgICAgICAgZ2d0aXRsZSgnSGlzdG9ncmFtIGZvciBsb2cxMF9DZWdrb3JhJykgKwogICAgICAgIGZhY2V0X2dyaWQoQmFua2lVZ3lmZWwgfiAuLCBzY2FsZXMgPSAnZnJlZScsIGxhYmVsbGVyID0gbGFiZWxsZXIoQmFua2lVZ3lmZWwgPSBjKCdUUlVFJyA9ICdCYW5raSBVZ3lmZWwnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRkFMU0UnID0gJ05vdCBCYW5raSBVZ3lmZWwnKSkpICsKICAgICAgICB4bGFiKCdsb2cxMF9DZWdrb3JhJykgKwogICAgICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICAgICAgdGhlbWVfaWdyYXkoKQpTeXMuc2xlZXAoMikKCnA3IDwtIGdncGxvdChjb21wYW55X2xpc3QpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBMZXRzemFtKSwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gJ29yYW5nZScsCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4zLAogICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArCiAgICAgICAgZ2d0aXRsZSgnSGlzdG9ncmFtIGZvciBsb2cxMF9MZXRzemFtJykgKwogICAgICAgIGZhY2V0X2dyaWQoQmFua2lVZ3lmZWwgfiAuLCBzY2FsZXMgPSAnZnJlZScsIGxhYmVsbGVyID0gbGFiZWxsZXIoQmFua2lVZ3lmZWwgPSBjKCdUUlVFJyA9ICdCYW5raSBVZ3lmZWwnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRkFMU0UnID0gJ05vdCBCYW5raSBVZ3lmZWwnKSkpICsKICAgICAgICB4bGFiKCdsb2cxMF9MZXRzemFtJykgKwogICAgICAgIHNjYWxlX3hfbG9nMTAoKSArCiAgICAgICAgdGhlbWVfaWdyYXkoKQpTeXMuc2xlZXAoMikKCnA4IDwtIGdncGxvdChjb21wYW55X2xpc3QpICsKICAgICAgICBnZW9tX2JhcihhZXMoeCA9IGFzLmZhY3RvcihDZWdmb3JtYSkpLAogICAgICAgICAgICAgICAgIGZpbGwgPSAnYmx1ZScsCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIGdndGl0bGUoJ0JyZWFrZG93biBvZiBDZWdmb3JtYScpICsKICAgICAgICB4bGFiKCdDZWdmb3JtYScpICsKICAgICAgICB0aGVtZV9pZ3JheSgpClN5cy5zbGVlcCgyKQoKcDkgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdCkgKwogICAgICAgIGdlb21fYmFyKGFlcyh4ID0gYXMuZmFjdG9yKEZvdGV2ZWtlbnlzZWdUZXh0KSksCiAgICAgICAgICAgICAgICAgZmlsbCA9ICdkYXJrZ3JlZW4nLAogICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gJ2lkZW50aXR5JywKICAgICAgICAgICAgICAgICBhbHBoYSA9IEkoMC43KSkgKwogICAgICAgIGZhY2V0X2dyaWQoQmFua2lVZ3lmZWwgfiAuLCBzY2FsZXMgPSAnZnJlZScsIGxhYmVsbGVyID0gbGFiZWxsZXIoQmFua2lVZ3lmZWwgPSBjKCdUUlVFJyA9ICdCYW5raSBVZ3lmZWwnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRkFMU0UnID0gJ05vdCBCYW5raSBVZ3lmZWwnKSkpICsKICAgICAgICBnZ3RpdGxlKCdCcmVha2Rvd24gb2YgRm90ZXZla2VueXNlZ1RleHQnKSArCiAgICAgICAgeGxhYignRm90ZXZla2VueXNlZ1RleHQnKSArCiAgICAgICAgdGhlbWVfaWdyYXkoKQpTeXMuc2xlZXAoMikKCnAxMCA8LSBnZ3Bsb3QoY29tcGFueV9saXN0KSArCiAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBhcy5mYWN0b3IoQmFua2lVZ3lmZWwpKSwKICAgICAgICAgICAgICAgICBmaWxsID0gJ2xpZ2h0Ymx1ZScsCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArCiAgICAgICAgZ2d0aXRsZSgnQnJlYWtkb3duIG9mIEJhbmtpVWd5ZmVsJykgKwogICAgICAgIHhsYWIoJ0JhbmtpVWd5ZmVsJykgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCmBgYAoKd2l0aG91dCBwbG90bHkgaW4gb25lIGdyaWQ6CmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgbXVsdGlwbG90KHAxLCBwMiwgcDMsIHA0LCBwNSwgcDYsIHA3LCBwOCwgcDksIHAxMCwgY29scyA9IDIpCmBgYCAgCgp0cmFuc2Zvcm1pbmcgaW50byBpbnRlcmFjdGl2ZSBncmFwaApgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcDEgPC0gZ2dwbG90bHkocDEpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxLmh0bWwnLCBzZXAgPSAnJykKaHRtbHdpZGdldHM6OnNhdmVXaWRnZXQoYXMud2lkZ2V0KHBwMSksIGZpbGVfb3V0KQpgYGAKCnRyYW5zZm9ybWluZyBpbnRvIGludGVyYWN0aXZlIGdyYXBoCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnBwMiA8LSBnZ3Bsb3RseShwMikKU3lzLnNsZWVwKDIpCmBgYAoKc2F2aW5nCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9wcDIuaHRtbCcsIHNlcCA9ICcnKQpodG1sd2lkZ2V0czo6c2F2ZVdpZGdldChhcy53aWRnZXQocHAyKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAzIDwtIGdncGxvdGx5KHAzKQpTeXMuc2xlZXAoMikKYGBgCgpzYXZpbmcKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL3BwMy5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDMpLCBmaWxlX291dCkKYGBgCgp0cmFuc2Zvcm1pbmcgaW50byBpbnRlcmFjdGl2ZSBncmFwaApgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcDQgPC0gZ2dwbG90bHkocDQpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHA0Lmh0bWwnLCBzZXAgPSAnJykKaHRtbHdpZGdldHM6OnNhdmVXaWRnZXQoYXMud2lkZ2V0KHBwNCksIGZpbGVfb3V0KQpgYGAKCnRyYW5zZm9ybWluZyBpbnRvIGludGVyYWN0aXZlIGdyYXBoCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnBwNSA8LSBnZ3Bsb3RseShwNSkKU3lzLnNsZWVwKDIpCmBgYAoKc2F2aW5nCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9wcDUuaHRtbCcsIHNlcCA9ICcnKQpodG1sd2lkZ2V0czo6c2F2ZVdpZGdldChhcy53aWRnZXQocHA1KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHA2IDwtIGdncGxvdGx5KHA2KQpTeXMuc2xlZXAoMikKYGBgCgpzYXZpbmcKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL3BwNi5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDYpLCBmaWxlX291dCkKYGBgCgp0cmFuc2Zvcm1pbmcgaW50byBpbnRlcmFjdGl2ZSBncmFwaApgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcDcgPC0gZ2dwbG90bHkocDcpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHA3Lmh0bWwnLCBzZXAgPSAnJykKaHRtbHdpZGdldHM6OnNhdmVXaWRnZXQoYXMud2lkZ2V0KHBwNyksIGZpbGVfb3V0KQpgYGAKCnRyYW5zZm9ybWluZyBpbnRvIGludGVyYWN0aXZlIGdyYXBoCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnBwOCA8LSBnZ3Bsb3RseShwOCkKU3lzLnNsZWVwKDIpCmBgYAoKc2F2aW5nCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9wcDguaHRtbCcsIHNlcCA9ICcnKQpodG1sd2lkZ2V0czo6c2F2ZVdpZGdldChhcy53aWRnZXQocHA4KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHA5IDwtIGdncGxvdGx5KHA5KQpTeXMuc2xlZXAoMikKYGBgCgpzYXZpbmcKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL3BwOS5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDkpLCBmaWxlX291dCkKYGBgCgp0cmFuc2Zvcm1pbmcgaW50byBpbnRlcmFjdGl2ZSBncmFwaApgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwcDEwIDwtIGdncGxvdGx5KHAxMCkKU3lzLnNsZWVwKDIpCmBgYAoKc2F2aW5nCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9wcDEwLmh0bWwnLCBzZXAgPSAnJykKaHRtbHdpZGdldHM6OnNhdmVXaWRnZXQoYXMud2lkZ2V0KHBwMTApLCBmaWxlX291dCkKYGBgCgpwbG90dGluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsYXlvdXQocHAxLCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKbGF5b3V0KHBwMiwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDMsIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHA0LCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKbGF5b3V0KHBwNSwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDYsIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHA3LCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKbGF5b3V0KHBwOCwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDksIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHAxMCwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmBgYAoKZGVhbGxvY2F0aW5nIG1lbW9yeSEKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kcm0oY29tcGFueV9saXN0LCBwMSwgcDIsIHAzLCBwNCwgcDUsIHA2LCBwNywgcDgsIHA5LCBwMTApCmBgYAoKIyBQcmVwYXJhdGlvbiBzdGVwcyBiZWZvcmUgRW5yaWNobWVudAoKZG93bmxvYWRpbmcgJiBkZWNvbXByZXNzaW5nIHBvc3Rjb2RlIGxpc3Qgb2YgYWxsIEh1bmdhcmlhbiBzZXR0bGVtZW50cyBpbnRvIGEgY29ycmVzcG9uZGluZyBmb2xkZXIKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KdGVtcCA8LSB0ZW1wZmlsZSgpCmRvd25sb2FkLmZpbGUoJ2h0dHA6Ly9kb3dubG9hZC5nZW9uYW1lcy5vcmcvZXhwb3J0L3ppcC9IVS56aXAnLCB0ZW1wKQp1bnppcCh6aXBmaWxlID0gdGVtcCwgZXhkaXIgPSBwYXN0ZShnZXR3ZCgpLCAnL190bXAvJywgc2VwID0gJycpKQpmaWxlLnJlbmFtZShwYXN0ZShnZXR3ZCgpLCAnL190bXAvSFUudHh0Jywgc2VwID0gJycpLCBwYXN0ZShnZXR3ZCgpLCAnL190bXAvcG9zdGNvZGVfbGlzdC5jc3YnLCBzZXAgPSAnJykpCmZpbGUucmVuYW1lKHBhc3RlKGdldHdkKCksICcvX3RtcC9yZWFkbWUudHh0Jywgc2VwID0gJycpLCBwYXN0ZShnZXR3ZCgpLCAnL190bXAvcG9zdGNvZGVfbGlzdF9yZWFkbWUudHh0Jywgc2VwID0gJycpKQpgYGAKCmltcG9ydGluZyBpbnRvIFIgZW52aXJvbm1lbnQKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcG9zdGNvZGVfbGlzdCA8LSBkYXRhLnRhYmxlKGZyZWFkKHBhc3RlKGdldHdkKCksICcvX3RtcC9wb3N0Y29kZV9saXN0LmNzdicsIHNlcCA9ICcnKSkpCnBvc3Rjb2RlX2xpc3RbLCAnOj0nIChJclN6YW0gPSBhcy5jaGFyYWN0ZXIoVjIpLCBNZWd5ZSA9IGFzLmNoYXJhY3RlcihWNCksIFZhcm9zID0gYXMuY2hhcmFjdGVyKFYzKSwgTGF0ID0gVjEwLCBMb24gPSBWMTEpXQpwb3N0Y29kZV9saXN0IDwtIHVuaXF1ZShwb3N0Y29kZV9saXN0WywgYygxMzoxNyldKQpgYGAKCmNoYW5naW5nIGFjY2VudGVkIGNoYXJhY3RlcnMgaW50byBub24tYWNjZW50ZWQgb25lcyBpbiB0aGUgZG93bmxvYWRlZCBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBvc3Rjb2RlX2xpc3RbLCAnOj0nIChNZWd5ZSA9IHN0cmlfdHJhbnNfZ2VuZXJhbChNZWd5ZSwgJ0xhdGluLUFTQ0lJJyksIFZhcm9zID0gc3RyaV90cmFuc19nZW5lcmFsKFZhcm9zLCAnTGF0aW4tQVNDSUknKSldCmBgYAoKbGV0J3MgcHV0IHRvZ2V0aGVyIHRob3NlIGNpdGllcyB3aGljaCBzaGFyZXMgdGhlIHNhbWUgcG9zdGNvZGUgYW5kIGF2ZXJhZ2Ugb3V0IGdlb2NvZGVzIG9uIHBvc3Rjb2RlIGxldmVsCnBvc3Rjb2RlcyBzaGFyZWQgYW1vbmcgYXQgbGVhc3QgMiBjaXRpZXMgd2lsbCBvbmx5IGhhdmUgb25lIChhdmVyYWdlZCkgZ2VvY29kZQpzbyBkaWZmZXJlbnQgc2V0dGxlbWVudHMgd2lsbCBoYXZlIHRoZSBzYW1lIGdlb2NvZGVzCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBvc3Rjb2RlX2xpc3QgPC0gZGF0YS50YWJsZShzcWxkZignU0VMRUNUICBJclN6YW0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLEdST1VQX0NPTkNBVChESVNUSU5DVCBNZWd5ZSkgQVMgTWVneWUgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICxHUk9VUF9DT05DQVQoRElTVElOQ1QgVmFyb3MpIEFTIFZhcm9zCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICxBVkcoTGF0KSBBUyBMYXQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLEFWRyhMb24pIEFTIExvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBGUk9NICBwb3N0Y29kZV9saXN0CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIElyU3phbScpKQpgYGAKCkxldCdzIGNoZWNrIHdpdGggZXllYmFsbGluZyB3aGV0aGVyIHdlIGhhdmUgYWxsIG9mIHRoZSBjaXRpZXMgb3Igbm90IChpdCBzZWVtcywgd2UgaGF2ZSkKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZHQgPC0gY29weShkYXRhLnRhYmxlKHBvc3Rjb2RlX2xpc3RbLCAuTiwgYnkgPSAuKExhdCwgTG9uLCBNZWd5ZSldKSkKSHVuZ2FyeSA8LSBnZXRfbWFwKGxvY2F0aW9uID0gYygxNiwgNDUuNywgMjMsIDQ4LjYpLCAKICAgICAgICAgICAgICAgICAgICB6b29tID0gOCwgCiAgICAgICAgICAgICAgICAgICAgc291cmNlID0gJ3N0YW1lbicsIAogICAgICAgICAgICAgICAgICAgIG1hcHR5cGUgPSAndG9uZXItbGl0ZScsIAogICAgICAgICAgICAgICAgICAgIG1lc3NhZ2luZyA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgIGxhbmd1YWdlID0gJ2h1LUhVJywKICAgICAgICAgICAgICAgICAgICBmaWxlbmFtZSA9IHBhc3RlKGdldHdkKCksICcvX3RtcC9IdW5nYXJ5VGVtcCcsIHNlcCA9ICcnKSkKYGBgCgpzYXZpbmcKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL190bXAvSHVuZ2FyeS5yZGEnLCBzZXAgPSAnJykKc2F2ZShIdW5nYXJ5LCBmaWxlID0gZmlsZV9vdXQpCgogIGdnbWFwKEh1bmdhcnkpICsKICBnZW9tX3BvaW50KGRhdGEgPSBkdCwKICAgICAgICAgICAgIGFlcyh4ID0gTG9uLAogICAgICAgICAgICAgICAgIHkgPSBMYXQsCiAgICAgICAgICAgICAgICAgY29sb3IgPSBNZWd5ZSwKICAgICAgICAgICAgICAgICBzaXplID0gTiksCiAgICAgICAgICAgICBhbHBoYSA9IDAuNywKICAgICAgICAgICAgIHN0cm9rZSA9IDEuMiwKICAgICAgICAgICAgIHNoYXBlID0gMSkgKwogIGdndGl0bGUoJ051bWJlciBvZiBQb3N0Y29kZXMgcGVyIENpdGllcyBvbiBIdW5nYXJ5JykKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIocG9zdGNvZGVfbGlzdFtzYW1wbGUoLk4sIDUpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKZGVhbGxvY2F0aW5nIG1lbW9yeSEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0Kcm0odGVtcCkKYGBgCgpkb3dubG9hZGluZyB0aG9zZSBjb21wYW5pZXMgbGlzdCB3aGljaCBlbXBsb3llZCBwZW9wbGUgd2l0aG91dCByZWdpc3RyYXRpb24gZnJvbSBodHRwOi8vbmF2Lmdvdi5odS9uYXYvYWRhdGJhemlzb2svYmVuZW1qZWxlbnRldHQKdGhpcyBsaW5rIG5hbWUgKGFuZCB0aGUgY29udGVudCBhbHNvKSBpcyBjaGFuZ2luZyB3ZWVrbHksIHNvIEkgdXNlZCBTZWxlbml1bQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpQYWdlIDwtICdodHRwOi8vbmF2Lmdvdi5odS9uYXYvYWRhdGJhemlzb2svYmVuZW1qZWxlbnRldHQnClhwYXRoVmFsdWUgPC0gJy8vKltAaWQ9InBvcnRhbCJdL2Rpdls2XS9kaXZbMl0vZGl2WzJdL2RpdlsyXS9kaXYvZGl2L2Rpdi90YWJsZS90Ym9keS90clsyXS90ZFsyXS9hJwpyRCA8LSByc0RyaXZlcihwb3J0ID0gNDU2N0wsIAogICAgICAgICAgICAgICBicm93c2VyID0gJ2Nocm9tZScsIAogICAgICAgICAgICAgICB2ZXJzaW9uID0gJ2xhdGVzdCcsIAogICAgICAgICAgICAgICBjaHJvbWV2ZXIgPSAnbGF0ZXN0JywKICAgICAgICAgICAgICAgZ2Vja292ZXIgPSAnbGF0ZXN0JywgCiAgICAgICAgICAgICAgIHBoYW50b212ZXIgPSAnMi4xLjEnLAogICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSwgCiAgICAgICAgICAgICAgIGNoZWNrID0gVFJVRSkKcmVtRHIgPC0gckRbWydjbGllbnQnXV0KcmVtRHIkbmF2aWdhdGUoUGFnZSkKV2ViRWxlbSA8LSByZW1EciRmaW5kRWxlbWVudCh1c2luZyA9ICd4cGF0aCcsIHZhbHVlID0gWHBhdGhWYWx1ZSkKVVJMIDwtIGFzLmNoYXJhY3RlcihXZWJFbGVtJGdldEVsZW1lbnRBdHRyaWJ1dGUoJ2hyZWYnKSkKcmVtRHIkY2xvc2UoKQpyRFtbJ3NlcnZlciddXSRzdG9wKCkKcm0ockQpCnJtKHJlbURyKQpybShXZWJFbGVtKQp1bnJlZ19lbXAgPC0gZGF0YS50YWJsZShyZWFkLnhsc3goVVJMKSkKYGBgCgpjaGFuZ2luZyBhY2NlbnRlZCBjaGFyYWN0ZXJzIGludG8gbm9uLWFjY2VudGVkIG9uZXMgaW4gdGhlIGRvd25sb2FkZWQgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpjb2xuYW1lcyh1bnJlZ19lbXApIDwtIGdzdWIoJygnLCAnXycsIGdzdWIoJyknLCAnJywgZ3N1YignICcsICcnLCBzdHJpX3RyYW5zX3RvdGl0bGUoZ3N1YignXFwuJywgJyAnLCBzdHJpX3RyYW5zX2dlbmVyYWwoY29sbmFtZXModW5yZWdfZW1wKSwgJ0xhdGluLUFTQ0lJJykpKSkpLCBmaXhlZCA9IFRSVUUpCnVucmVnX2VtcFssICc6PScgKEFkb3pvTmV2ZSA9IHN0cmlfdHJhbnNfZ2VuZXJhbChBZG96b05ldmUsICdMYXRpbi1BU0NJSScpLCBTemVraGVseV9MYWtjaW0gPSBzdHJpX3RyYW5zX2dlbmVyYWwoU3pla2hlbHlfTGFrY2ltLCAnTGF0aW4tQVNDSUknKSldCmBgYAoKc2F2aW5nCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9fdG1wL3VucmVnX2VtcC5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUodW5yZWdfZW1wLCAKICAgICAgIGZpbGUgPSBmaWxlX291dCwKICAgICAgIG5UaHJlYWQgPSBnZXREVHRocmVhZHMoKSkKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIodW5yZWdfZW1wW3NhbXBsZSguTiwgNSldLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgpleHRyYWN0IHRoZSBuZWNlc3NhcnkgZGF0YSBvdXQgb2YgdW5yZWdfZW1wCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnVucmVnX2VtcCA8LSB1bnJlZ19lbXBbLCBuYW1lcyh1bnJlZ19lbXApWzRdLCB3aXRoID0gRkFMU0VdCnVucmVnX2VtcFssIHVucmVnX2VtcCA6PSBUUlVFXQp1bnJlZ19lbXBbLCBBZG9zemFtIDo9IHN1YnN0cihzdHJpX3RyaW0oQWRvc3phbV9BZG9hem9ub3NpdG9KZWwpLCAxLCA4KV0KdW5yZWdfZW1wWywgQWRvc3phbV9BZG9hem9ub3NpdG9KZWwgOj0gTlVMTF0KdW5yZWdfZW1wIDwtIHVuaXF1ZSh1bnJlZ19lbXApCmBgYAoKc2F2aW5nCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9fdG1wL3VucmVnX2VtcC5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUodW5yZWdfZW1wLCAKICAgICAgIGZpbGUgPSBmaWxlX291dCwKICAgICAgIG5UaHJlYWQgPSBnZXREVHRocmVhZHMoKSkKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIodW5yZWdfZW1wW3NhbXBsZSguTiwgNSldLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgojIEVucmljaGluZyBEYXRhIGZyb20gaW50ZXJuYWwgYW5kIGV4dGVybmFsIHNvdXJjZXMKCmNyZWF0aW5nIGEgbmV3LCBtb2RpZmllZCBpbnN0YW5jZSBvZiBjb21wYW55IGxpc3QgLSBpbXBvcnRpbmcKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZmlsZV9pbiA9IHBhc3RlKGdldHdkKCksICcvX3RtcC9jb21wYW55X2xpc3QuUkRhdGEnLCBzZXAgPSAnJykKY29tcGFueV9saXN0XzEgPC0gZGF0YS50YWJsZShmcmVhZChmaWxlX2luKSkKYGBgCgpjcmVhdGluZyBhIG5ldywgbW9kaWZpZWQgaW5zdGFuY2Ugb2YgY29tcGFueSBsaXN0IC0gY3JlYXRpbmcgbmV3IGNvbHVtbnMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY29tcGFueV9saXN0XzFbbmNoYXIoc3ViKCcgLionLCAnJywgc3RyaV90cmltKFN6ZWtoZWx5KSkpID09IDQsIElyU3phbSA6PSBzdWJzdHIoc3ViKCcgLionLCAnJywgc3RyaV90cmltKFN6ZWtoZWx5KSksIDEsIDQpXQpjb21wYW55X2xpc3RfMVtBZG9zemFtID09IHN1YnN0cihBZG9zemFtSG9zc3p1LDEgLCA4KSAmIG5jaGFyKEFkb3N6YW1Ib3NzenUpID09IDEzLCBBRkFLb2QgOj0gc3Vic3RyKEFkb3N6YW1Ib3NzenUsIDEwLCAxMCldCmNvbXBhbnlfbGlzdF8xW0Fkb3N6YW0gPT0gc3Vic3RyKEFkb3N6YW1Ib3NzenUsMSAsIDgpICYgbmNoYXIoQWRvc3phbUhvc3N6dSkgPT0gMTMsIE5BVlRlcnVsZXRLb2QgOj0gc3Vic3RyKEFkb3N6YW1Ib3NzenUsIDEyLCAxMyldCmNvbXBhbnlfbGlzdF8xWywgQWRvc3phbUhvc3N6dSA6PSBOVUxMXQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihjb21wYW55X2xpc3RfMVtzYW1wbGUoLk4sIDUpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKbGV0J3MgZGVub3RlIHRob3NlIGNvbXBhbmllcyB3aGljaCB1c2VkIHVucmVnaXN0ZXJlZCBlbXBsb3llZXMgLSByaWdodCBqb2luCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdF8xWywgQWRvc3phbSA6PSBhcy5jaGFyYWN0ZXIoQWRvc3phbSldCnNldGtleSh1bnJlZ19lbXAsIEFkb3N6YW0pCnNldGtleShjb21wYW55X2xpc3RfMSwgQWRvc3phbSkKY29tcGFueV9saXN0XzEgPC0gdW5yZWdfZW1wW2NvbXBhbnlfbGlzdF8xXQpgYGAKCnJlcGxhY2luZyBOQSB3aXRoIEZBTFNFIGluIHVucmVnX2VtcCBjb2x1bW4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY29tcGFueV9saXN0XzFbaXMubmEodW5yZWdfZW1wKSwgdW5yZWdfZW1wIDo9IEZBTFNFXQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihjb21wYW55X2xpc3RfMVt1bnJlZ19lbXAsIF1bc2FtcGxlKC5OLCA1KV0sIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCmpvaW5pbmcgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBkYXRhIG9mIHBvc3Rjb2RlcyB0byBjb21wYW55IGxpc3QgLSByaWdodCBqb2luCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnNldGtleShwb3N0Y29kZV9saXN0LCBJclN6YW0pCnNldGtleShjb21wYW55X2xpc3RfMSwgSXJTemFtKQpjb21wYW55X2xpc3RfMSA8LSBwb3N0Y29kZV9saXN0W2NvbXBhbnlfbGlzdF8xXQppIDwtIGNvbXBhbnlfbGlzdF8xW2lzLm5hKExhdCksIC5OXQpqIDwtIGNvbXBhbnlfbGlzdF8xWywgLk5dCnBhbmRlcihwYXN0ZSgnVGhlcmUgYXJlJywgaSwgJ2NsaWVudHMgb3V0IG9mJywgaiwgJ3dpdGhvdXQgZ2VvY29kZSBwcm94eScsIHNlcCA9ICcgJyksIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCkxldCdzIGNoZWNrIHdpdGggZXllYmFsbGluZyBpbiB3aGljaCBwb3N0Y29kZSBhcmVhIGFyZSB0aGUgY3VycmVudCBjbGllbnRzJyBocQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpkdCA8LSBjb3B5KGRhdGEudGFibGUoY29tcGFueV9saXN0XzFbIWlzLm5hKExhdCksIC5OLCBieSA9IC4oQmFua2lVZ3lmZWwsIExhdCwgTG9uKV0pKQpnZ21hcChIdW5nYXJ5KSArCiAgZ2VvbV9wb2ludChkYXRhID0gZHQsCiAgICAgICAgICAgICBhZXMoeCA9IExvbiwKICAgICAgICAgICAgICAgICB5ID0gTGF0LAogICAgICAgICAgICAgICAgIGNvbG9yID0gQmFua2lVZ3lmZWwsCiAgICAgICAgICAgICAgICAgc2l6ZSA9IE4pLAogICAgICAgICAgICAgYWxwaGEgPSAwLjcsCiAgICAgICAgICAgICBzdHJva2UgPSAxLjIsCiAgICAgICAgICAgICBzaGFwZSA9IDEpICsKICBnZ3RpdGxlKCdOdW1iZXIgb2YgQ29tcGFuaWVzIHBlciBQb3N0Y29kZSBBcmVhcyBvbiBIdW5nYXJ5JykKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIoY29tcGFueV9saXN0XzFbIWlzLm5hKExhdCksIF1bc2FtcGxlKC5OLCA1KV0sIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCmpvaW5pbmcgbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBkYXRhIG9mIHBvc3Rjb2RlcyB0byBicmFuY2ggcG9zdGNvZGUgbGlzdCAtIHJpZ2h0IGpvaW4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0Kc2V0a2V5KGJyYW5jaF96aXBfbGlzdCwgSXJTemFtKQpzZXRrZXkocG9zdGNvZGVfbGlzdCwgSXJTemFtKQpicmFuY2hfcG9zdGNvZGVfbGlzdCA8LSBwb3N0Y29kZV9saXN0W2JyYW5jaF96aXBfbGlzdF0KYGBgCgpzYXZpbmcKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL190bXAvYnJhbmNoX3Bvc3Rjb2RlX2xpc3QuUkRhdGEnLCBzZXAgPSAnJykKZndyaXRlKGJyYW5jaF9wb3N0Y29kZV9saXN0LCAKICAgICAgIGZpbGUgPSBmaWxlX291dCwKICAgICAgIG5UaHJlYWQgPSBnZXREVHRocmVhZHMoKSkKYGBgCgpMZXQncyBjaGVjayB3aXRoIGV5ZWJhbGxpbmcgaW4gd2hpY2ggcG9zdGNvZGUgYXJlYSBhcmUgdGhlIGJyYW5jaGVzIG9uIHRoZSBiYW5rCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmdnbWFwKEh1bmdhcnkpICsKICBnZW9tX3BvaW50KGRhdGEgPSBicmFuY2hfcG9zdGNvZGVfbGlzdCwKICAgICAgICAgICAgIGFlcyh4ID0gTG9uLAogICAgICAgICAgICAgICAgIHkgPSBMYXQpLAogICAgICAgICAgICAgc2l6ZSA9IDEsCiAgICAgICAgICAgICBjb2xvciA9ICdncmVlbicsCiAgICAgICAgICAgICBhbHBoYSA9IDAuNywKICAgICAgICAgICAgIHN0cm9rZSA9IDEuMiwKICAgICAgICAgICAgIHNoYXBlID0gMSkgKwogIGdndGl0bGUoJ0JhbmsgQnJhbmNoZXMgQWxsIE92ZXIgSHVuZ2FyeScpCmBgYAoKbGV0J3MgY2hlY2sgYSBzYW1wbGUgb2YgdGhlIGRhdGEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcGFuZGVyKGJyYW5jaF9wb3N0Y29kZV9saXN0W3NhbXBsZSguTiwgNSldLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgpkZWFsbG9jYXRpbmcgbWVtb3J5IQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpybShIdW5nYXJ5KQpgYGAKCmNhbGN1bGF0aW5nIGRpc3RhbmNlIHByb3h5IGJldHdlZW4gZmlybXMgYWRkcmVzcyBhbmQgYSB0aGUgbmVhcmVzdCBicmFuY2ggb2YgYmFuawpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwb3N0Y29kZV9nZW9jb2RlX2xpc3QgPC0gY29weShkYXRhLnRhYmxlKHVuaXF1ZShjb21wYW55X2xpc3RfMVshaXMubmEoTGF0KSwgLihJclN6YW0sIExhdCwgTG9uKV0pKSkKc2V0a2V5KHBvc3Rjb2RlX2dlb2NvZGVfbGlzdCwgSXJTemFtKQoKZGlzdF9tYXRyaXggPC0gY29weShkYXRhLnRhYmxlKHBvc3Rjb2RlX2dlb2NvZGVfbGlzdFssIElyU3phbV0pKQpjb2xuYW1lcyhkaXN0X21hdHJpeCkgPC0gJ0lyU3phbScKZGlzdF9tYXRyaXhbLCBMZWdrb3pGaW9rVGF2UHJveHkgOj0gMTAwMDAwMDBdCgpmb3IgKGkgaW4gMTpucm93KHBvc3Rjb2RlX2dlb2NvZGVfbGlzdCkpIHsKICBmb3IgKGogaW4gMTpucm93KGJyYW5jaF9wb3N0Y29kZV9saXN0KSkgewogICAgZGlzdF9tYXRyaXhbaSwgYnJhbmNoX3Bvc3Rjb2RlX2xpc3RbaiwgSXJTemFtXV0gPC0gZ2Rpc3QobG9uLjEgPSBwb3N0Y29kZV9nZW9jb2RlX2xpc3RbaSwgTG9uXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhdC4xID0gcG9zdGNvZGVfZ2VvY29kZV9saXN0W2ksIExhdF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsb24uMiA9IGJyYW5jaF9wb3N0Y29kZV9saXN0W2osIExvbl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYXQuMiA9IGJyYW5jaF9wb3N0Y29kZV9saXN0W2osIExhdF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bml0cyA9ICdrbScpCiAgfQogIGRpc3RfbWF0cml4W2ksIExlZ2tvekZpb2tUYXZQcm94eSA6PSBtaW4oYXMubnVtZXJpYyhkaXN0X21hdHJpeFtpLCAzOihuY29sKGRpc3RfbWF0cml4KSksIHdpdGggPSBGQUxTRV0pLCBuYS5ybSA9IFRSVUUpXQp9CmBgYAoKc2F2aW5nCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9fdG1wL2Rpc3RfbWF0cml4LlJEYXRhJywgc2VwID0gJycpCmZ3cml0ZShkaXN0X21hdHJpeCwgCiAgICAgICBmaWxlID0gZmlsZV9vdXQsCiAgICAgICBuVGhyZWFkID0gZ2V0RFR0aHJlYWRzKCkpCmBgYAoKbGV0J3MgY2hlY2sgYSBzYW1wbGUgb2YgdGhlIGRpc3RhbmNlIG1hdHJpeCBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihkaXN0X21hdHJpeFtzYW1wbGUoLk4sIDUpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKbGV0J3MgZW5yaWNoaW5nIHRoZSBjb21wYW55IGxpc3QgZGF0YSB3aXRoIG5lYXJlc3QgYnJhbmNoIGRpc3RhbmNlIHByb3h5CmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmR0IDwtIGNvcHkoZGF0YS50YWJsZShkaXN0X21hdHJpeFssIDE6Ml0pKQpzZXRrZXkoZHQsIElyU3phbSkKc2V0a2V5KGNvbXBhbnlfbGlzdF8xLCBJclN6YW0pCmNvbXBhbnlfbGlzdF8xIDwtIGR0W2NvbXBhbnlfbGlzdF8xXQpgYGAKCnNhdmluZwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvX3RtcC9jb21wYW55X2xpc3RfMS5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUoY29tcGFueV9saXN0XzEsIAogICAgICAgZmlsZSA9IGZpbGVfb3V0LAogICAgICAgblRocmVhZCA9IGdldERUdGhyZWFkcygpKQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihjb21wYW55X2xpc3RfMVtzYW1wbGUoLk4sIDUpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKZGVhbGxvY2F0aW5nIG1lbW9yeQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpybShkaXN0X21hdHJpeCkKYGBgCgpsZXQnIHMgZGVub3RlIHdoZXRoZXIgcGFyZW50IGNvbXBhbmllcyBhcmUgY2xpZW50cyBhbHNvIG9yIG5vdApgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpkdCA8LSBjb21wYW55X2xpc3RfMVtCYW5raVVneWZlbCA9PSBUUlVFICYgbmNoYXIoQWRvc3phbSkgPT0gOCwgLihCYW5raVVneWZlbCwgQWRvc3phbSldCnNldG5hbWVzKGR0LCAnQWRvc3phbScsICdBZG9zemFtVHVsYWonKQpzZXRuYW1lcyhkdCwgJ0JhbmtpVWd5ZmVsJywgJ1R1bGFqQ2VnZXNCYW5raVVneWZlbCcpCm93bmVyX2NvbXBhbnlfbGlzdF8xIDwtIGNvcHkoZGF0YS50YWJsZShvd25lcl9jb21wYW55X2xpc3QpKQpgYGAKCnJpZ2h0IGpvaW4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0Kc2V0a2V5KGR0LCBBZG9zemFtVHVsYWopCnNldGtleShvd25lcl9jb21wYW55X2xpc3RfMSwgQWRvc3phbVR1bGFqKQpvd25lcl9jb21wYW55X2xpc3RfMSA8LSBkdFtvd25lcl9jb21wYW55X2xpc3RfMV0Kb3duZXJfY29tcGFueV9saXN0XzFbaXMubmEoVHVsYWpDZWdlc0JhbmtpVWd5ZmVsKSwgVHVsYWpDZWdlc0JhbmtpVWd5ZmVsIDo9IEZBTFNFXQpgYGAKCnNhdmluZwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvX3RtcC9vd25lcl9jb21wYW55X2xpc3RfMS5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUob3duZXJfY29tcGFueV9saXN0XzEsIAogICAgICAgZmlsZSA9IGZpbGVfb3V0LAogICAgICAgblRocmVhZCA9IGdldERUdGhyZWFkcygpKQpgYGAKCmxldCdzIGNoZWNrIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcihvd25lcl9jb21wYW55X2xpc3RfMVssIC5OLCBieSA9IChUdWxhakNlZ2VzQmFua2lVZ3lmZWwpXSwgc3BsaXQudGFibGUgPSAyMDApCmBgYAoKbGV0J3MgY291bnQgdGhlIG51bWJlciBvZiB0aG9zZSBvd25lciBjb21wYW5pZXMgd2hpY2ggYXJlIGN1c3RvbWVycyBvZiB0aGUgQmFuayBieSBhZmZpbGlhdGUgY29tcGFuaWVzCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmR0IDwtIGNvcHkoZGF0YS50YWJsZShvd25lcl9jb21wYW55X2xpc3RfMVtUdWxhakNlZ2VzQmFua2lVZ3lmZWwgPT0gVFJVRSwgXSkpCmR0IDwtIGRhdGEudGFibGUoc3FsZGYoJ1NFTEVDVCBBZG9zemFtCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICxDT1VOVChESVNUSU5DVCBUdWxhakNlZ2VzQmFua2lVZ3lmZWwpIEFTIFR1bGFqQ2VnZXNCYW5raVVneWZlbE5yCiAgICAgICAgICAgICAgICAgICAgICAgIEZST00gIGR0CiAgICAgICAgICAgICAgICAgICAgICAgIEdST1VQIEJZIEFkb3N6YW0nKSkKYGBgCgpsZXQncyBjaGVjayBhIHNhbXBsZSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIoZHRbc2FtcGxlKC5OLCA1KV0sIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCmxldCdzIGpvaW4gdGhlIGNvbXBhbnkgbGlzdCB3aXRoIHRoZSBtb2RpZmllZCBvd25lciBjb21wYW55IGxpc3QKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0Kc2V0a2V5KGR0LCBBZG9zemFtKQpzZXRrZXkoY29tcGFueV9saXN0XzEsIEFkb3N6YW0pCmNvbXBhbnlfbGlzdF8xIDwtIGR0W2NvbXBhbnlfbGlzdF8xXQpjb21wYW55X2xpc3RfMVtpcy5uYShUdWxhakNlZ2VzQmFua2lVZ3lmZWxOciksIFR1bGFqQ2VnZXNCYW5raVVneWZlbE5yIDo9IDBdCmBgYAoKc2F2aW5nCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9fdG1wL2NvbXBhbnlfbGlzdF8xLlJEYXRhJywgc2VwID0gJycpCmZ3cml0ZShjb21wYW55X2xpc3RfMSwgCiAgICAgICBmaWxlID0gZmlsZV9vdXQsCiAgICAgICBuVGhyZWFkID0gZ2V0RFR0aHJlYWRzKCkpCmBgYAoKbGV0J3MgY2hlY2sgYSBzYW1wbGUgb2YgdGhlIGRhdGEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcGFuZGVyKGNvbXBhbnlfbGlzdF8xW3NhbXBsZSguTiwgNSldLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgpkZWFsbG9jYXRpbmcgbWVtb3J5CmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnJtKG93bmVyX2NvbXBhbnlfbGlzdCwgb3duZXJfY29tcGFueV9saXN0XzEpCmBgYAoKcHJlcmVxdWlzaXRlIG9mIGRldGVjdGlvbiBvZiBBZG9zemFtIHZhbHVlcyBiZWluZyB1bmlxdWUgaXMgdG8gb3JkZXIgdGhlbSBieSB0aGF0IGNvbHVtbgpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpjb21wYW55X2xpc3RfMSA8LSBjb21wYW55X2xpc3RfMVtvcmRlcihBZG9zemFtKV0KYGBgCgpsZXQncyBjaGVjayBjb2x1bW4gdmFsdWVzIHZhbGlkaXR5IGFnYWluCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmN0IDwtIGNoZWNrX3RoYXQoY29tcGFueV9saXN0XzEsIAogICAgICAgICAgICAgICAgIHZhbGlkX25vdGF0aW9uX0JhbmtpVWd5ZmVsID0gQmFua2lVZ3lmZWwgPT0gMAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgQmFua2lVZ3lmZWwgPT0gMSwKICAgICAgICAgICAgICAgICBkaXN0aW5jdF9BZG9zemFtID0gcmxlKHNvcnQoQWRvc3phbSkpW1sxXV0gPT0gMSwKICAgICAgICAgICAgICAgICBjb21wbGV0ZV9BZG9zemFtID0gc3RyaV9sZW5ndGgoQWRvc3phbSkgPT0gOCwKICAgICAgICAgICAgICAgICBpbml0Y2FwX0NlZ2Zvcm1hID0gQ2VnZm9ybWEgPT0gc3RyaV90cmFuc190b3RpdGxlKENlZ2Zvcm1hKSwKICAgICAgICAgICAgICAgICB2YWxpZF9Ba3RpdlR1bGFqZG9ub3NTemFtID0gQWt0aXZUdWxhamRvbm9zU3phbSA+PSAxLAogICAgICAgICAgICAgICAgIHZhbGlkX0FrdGl2UHJpdmF0VHVsYWpkb25vcyA9IEFrdGl2VHVsYWpkb25vc1N6YW0gPj0gQWt0aXZQcml2YXRUdWxhamRvbm9zLAogICAgICAgICAgICAgICAgIGZpbGxlZF9Ba3RpdlR1bGFqZG9ub3NTemFtX2FuZF9Ba3RpdlByaXZhdFR1bGFqZG9ub3MgPSBzdHJpX2xlbmd0aChBa3RpdlR1bGFqZG9ub3NTemFtKSA+PSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIHN0cmlfbGVuZ3RoKEFrdGl2UHJpdmF0VHVsYWpkb25vcykgPj0gMSwKICAgICAgICAgICAgICAgICBub3RfbmVnYXRpdmVfQ2Vna29yYSA9IENlZ2tvcmEgPj0gMCwKICAgICAgICAgICAgICAgICB1bmRlcl9VQ0xfQ2Vna29yYSA9IENlZ2tvcmEgPD0gbWVhbihDZWdrb3JhLCBuYS5ybSA9IFRSVUUpICsgMyAqIHNkKENlZ2tvcmEsIG5hLnJtID0gVFJVRSksCiAgICAgICAgICAgICAgICAgdmFsaWRfTGV0c3phbSA9IExldHN6YW0gPj0gMSwKICAgICAgICAgICAgICAgICBmaWxsZWRfRm90ZXZla2VueXNlZ1RleHQgPSBzdHJpX2xlbmd0aChGb3RldmVrZW55c2VnVGV4dCkgPj0gMSwKICAgICAgICAgICAgICAgICB2YWxpZF9wb3N0Y29kZSA9IG5jaGFyKHN1YignIC4qJywgJycsIHN0cmlfdHJpbShTemVraGVseSkpKSA9PSA0LAogICAgICAgICAgICAgICAgIHZhbGlkX1N6ZWtoZWx5VmFyb3MgPSBzdHJpX2xlbmd0aChTemVraGVseVZhcm9zKSA+PSAyCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIGdyZXBsKCdcXGQnLCBTemVraGVseVZhcm9zKSA9PSBGQUxTRSwKICAgICAgICAgICAgICAgICBpbml0Y2FwX1N6ZWtoZWx5VmFyb3MgPSBTemVraGVseVZhcm9zID09IHN0cmlfdHJhbnNfdG90aXRsZShTemVraGVseVZhcm9zKSwgCiAgICAgICAgICAgICAgICAgdmFsaWRfU3pla2hlbHkgPSBzdHJpX2xlbmd0aChTemVraGVseSkgPj0gMTEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIGdyZXBsKCdcXEQnLCBzdWJzdHIoc3RyaV90cmltKFN6ZWtoZWx5KSwgMSwgNCkpID09IEZBTFNFCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiBncmVwbCgnXFxeJywgU3pla2hlbHkpID09IEZBTFNFLAogICAgICAgICAgICAgICAgIGluaXRjYXBfU3pla2hlbHkgPSBTemVraGVseSA9PSBzdHJpX3RyYW5zX3RvdGl0bGUoU3pla2hlbHkpLCAKICAgICAgICAgICAgICAgICBmaWxsZWRfQXJiZXZldGVsID0gc3RyaV9sZW5ndGgoQXJiZXZldGVsKSA+PSAxLAogICAgICAgICAgICAgICAgIGZpbGxlZF9NZXJsZWdFcmVkbWVueSA9IHN0cmlfbGVuZ3RoKE1lcmxlZ0VyZWRtZW55KSA+PSAxLAogICAgICAgICAgICAgICAgIGZpbGxlZF9TYWphdFRva2UgPSBzdHJpX2xlbmd0aChTYWphdFRva2UpID49IDEsCiAgICAgICAgICAgICAgICAgZmlsbGVkX0ZvdGV2ZWtlbnlzZWdUZXh0X2FuZF9Ba3RpdlR1bGFqZG9ub3NTemFtX2FuZF9DZWdmb3JtYSA9IHN0cmlfbGVuZ3RoKEZvdGV2ZWtlbnlzZWdUZXh0KSA+PSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIHN0cmlfbGVuZ3RoKEFrdGl2VHVsYWpkb25vc1N6YW0pID49IDEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgc3RyaV9sZW5ndGgoQ2VnZm9ybWEpID49IDEsCiAgICAgICAgICAgICAgICAgZmlsbGVkX0xhdF9Mb24gPSAhaXMubmEoTGF0KQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgIWlzLm5hKExvbiksCiAgICAgICAgICAgICAgICAgZmlsbGVkX3VucmVnX2VtcCA9ICFpcy5uYSh1bnJlZ19lbXApLAogICAgICAgICAgICAgICAgIGZpbGxlZF9BRkFLb2QgPSAhaXMubmEoQUZBS29kKSwKICAgICAgICAgICAgICAgICBmaWxsZWRfTWVneWUgPSAhaXMubmEoTWVneWUpLAogICAgICAgICAgICAgICAgIGZpbGxlZF9UdWxhakNlZ2VzQmFua2lVZ3lmZWxOciA9ICFpcy5uYShUdWxhakNlZ2VzQmFua2lVZ3lmZWxOcikKICAgICAgICAgICAgICAgICApCmR0IDwtIGRhdGEudGFibGUoc3VtbWFyeShjdCkpCmR0IDwtIGR0WywgYygxOjUsIDgpXQpgYGAKCmxldCdzIHNlZSB0aGUgdmFsaWRpdHkgb2YgdGhlIGRhdGEKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KcGFuZGVyKGR0LCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgoKIyBJbXB1dGF0aW5nIERhdGEKClNvbWUgQWt0aXZQcml2YXRUdWxhamRvbm9zLCBBa3RpdlR1bGFqZG9ub3NTemFtIHZhbHVlcyBhcmUgbWlzc2luZyAvIEkgaW1wdXRlIHRoZW0gd2l0aCB0aGUgbWVkaWFuIHZhbHVlIGluIGVhY2ggTWVneWUgd2l0aCBzYW1lIEZvdGV2ZWtlbnlzZWdUZXh0IGFuZCBBRkFLb2QKCnN1YnNldHRpbmcgaW50byBkdApgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpkdCA8LSBjb3B5KGRhdGEudGFibGUoY29tcGFueV9saXN0XzFbIWlzLm5hKEFGQUtvZCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmICFpcy5uYShNZWd5ZSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmICFpcy5uYShGb3RldmVrZW55c2VnVGV4dCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4oQWRvc3phbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFrdGl2UHJpdmF0VHVsYWpkb25vcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFrdGl2VHVsYWpkb25vc1N6YW0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBRkFLb2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBNZWd5ZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEZvdGV2ZWtlbnlzZWdUZXh0KV0pKQpgYGAKCmZhY3Rvcml6YXRpb24gb2YgY2F0ZWdvcmljYWwgdmFyaWFibGVzCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmR0WywgJzo9JyAoQUZBS29kID0gYXMuZmFjdG9yKEFGQUtvZCksCiAgICAgICAgICAgTWVneWUgPSBhcy5mYWN0b3IoTWVneWUpLAogICAgICAgICAgIEZvdGV2ZWtlbnlzZWdUZXh0ID0gYXMuZmFjdG9yKEZvdGV2ZWtlbnlzZWdUZXh0KSldCmBgYAoKY2hlY2tpbmcgdGhlIG51bWJlciBvZiBtaXNzaW5nIHZhbHVlcyBvZiBBa3RpdlByaXZhdFR1bGFqZG9ub3MKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KbiA8LSBkdFtpcy5uYShBa3RpdlByaXZhdFR1bGFqZG9ub3MpLCAuTl0KcGFuZG9jLmhlYWRlcihwYXN0ZSgnVGhlIHByaW9yIG51bWJlciBvZiBtaXNzaW5nIEFrdGl2UHJpdmF0VHVsYWpkb25vcyB2YWx1ZXM6ICcsIG4sIHNlcCA9ICcnKSkKYGBgCgppbXB1dGF0aW9uCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZvcm11bGEgPC0gQWt0aXZQcml2YXRUdWxhamRvbm9zIH4gQUZBS29kICsgTWVneWUgKyBGb3RldmVrZW55c2VnVGV4dApkdCA8LSBpbXB1dGVfbWVkaWFuKGR0LCBmb3JtdWxhKQpTeXMuc2xlZXAoMykKYGBgCgpyb3VuZGluZyB0aGUgaW1wdXRlZCB2YWx1ZXMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZHRbIWlzLm5hKEFrdGl2UHJpdmF0VHVsYWpkb25vcyksIEFrdGl2UHJpdmF0VHVsYWpkb25vcyA6PSByb3VuZChBa3RpdlByaXZhdFR1bGFqZG9ub3MsIDApXQpgYGAKCmNoZWNraW5nIHRoZSBudW1iZXIgb2YgbWlzc2luZyB2YWx1ZXMgb2YgQWt0aXZUdWxhamRvbm9zU3phbQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpuIDwtIGR0W2lzLm5hKEFrdGl2VHVsYWpkb25vc1N6YW0pLCAuTl0KcGFuZG9jLmhlYWRlcihwYXN0ZSgnVGhlIHByaW9yIG51bWJlciBvZiBtaXNzaW5nIEFrdGl2VHVsYWpkb25vc1N6YW0gdmFsdWVzOiAnLCBuLCBzZXAgPSAnJykpCmBgYAoKaW1wdXRhdGlvbgpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmb3JtdWxhIDwtIEFrdGl2VHVsYWpkb25vc1N6YW0gfiBBRkFLb2QgKyBNZWd5ZSArIEZvdGV2ZWtlbnlzZWdUZXh0CmR0IDwtIGltcHV0ZV9tZWRpYW4oZHQsIGZvcm11bGEpClN5cy5zbGVlcCgzKQpgYGAKCnJvdW5kaW5nIHRoZSBpbXB1dGVkIHZhbHVlcwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpkdFshaXMubmEoQWt0aXZUdWxhamRvbm9zU3phbSksIEFrdGl2VHVsYWpkb25vc1N6YW0gOj0gcm91bmQoQWt0aXZUdWxhamRvbm9zU3phbSwgMCldCmBgYAoKc3Vic2V0dGluZwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpkdCA8LSBkYXRhLnRhYmxlKGR0WywgLihBZG9zemFtLCBBa3RpdlByaXZhdFR1bGFqZG9ub3MsIEFrdGl2VHVsYWpkb25vc1N6YW0pXSkKYGBgCgppbml0aWFsIHByZXBhcmF0aW9uIG9mIGNvbXBhbnkgbGlzdCAyCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdF8yIDwtIGNvcHkoZGF0YS50YWJsZShjb21wYW55X2xpc3RfMSkpCmBgYAoKcmlnaHQgam9pbiB0aGUgaW1wdXRlZCB2YWx1ZXMgaW50byBjb21wYW55X2xpc3RfMgpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpzZXRrZXkoZHQsIEFkb3N6YW0pCnNldGtleShjb21wYW55X2xpc3RfMiwgQWRvc3phbSkKY29tcGFueV9saXN0XzIgPC0gZGF0YS50YWJsZShkdFtjb21wYW55X2xpc3RfMl0pCmNvbXBhbnlfbGlzdF8yW2lzLm5hKEFrdGl2UHJpdmF0VHVsYWpkb25vcyksIEFrdGl2UHJpdmF0VHVsYWpkb25vcyA6PSBpLkFrdGl2UHJpdmF0VHVsYWpkb25vc10KY29tcGFueV9saXN0XzJbaXMubmEoQWt0aXZUdWxhamRvbm9zU3phbSksIEFrdGl2VHVsYWpkb25vc1N6YW0gOj0gaS5Ba3RpdlR1bGFqZG9ub3NTemFtXQpjb21wYW55X2xpc3RfMlssICc6PScgKGkuQWt0aXZQcml2YXRUdWxhamRvbm9zID0gTlVMTCwgaS5Ba3RpdlR1bGFqZG9ub3NTemFtID0gTlVMTCldCmBgYAoKc2F2aW5nCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CmZpbGVfb3V0ID0gcGFzdGUoZ2V0d2QoKSwgJy9fdG1wL2NvbXBhbnlfbGlzdF8yLlJEYXRhJywgc2VwID0gJycpCmZ3cml0ZShjb21wYW55X2xpc3RfMiwgCiAgICAgICBmaWxlID0gZmlsZV9vdXQsCiAgICAgICBuVGhyZWFkID0gZ2V0RFR0aHJlYWRzKCkpCmBgYAoKbGV0J3MgY2hlY2sgdGhlIGltcHV0YXRpb24gYWdhaW4KYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KY3QgPC0gY2hlY2tfdGhhdChjb21wYW55X2xpc3RfMiwKICAgICAgICAgICAgICAgICBmaWxsZWRfQWt0aXZUdWxhamRvbm9zU3phbSA9IHN0cmlfbGVuZ3RoKEFrdGl2VHVsYWpkb25vc1N6YW0pID49IDEsCiAgICAgICAgICAgICAgICAgZmlsbGVkX0FrdGl2UHJpdmF0VHVsYWpkb25vcyA9IHN0cmlfbGVuZ3RoKEFrdGl2UHJpdmF0VHVsYWpkb25vcykgPj0gMSwKICAgICAgICAgICAgICAgICBmaWxsZWRfQWt0aXZUdWxhamRvbm9zU3phbV9hbmRfQWt0aXZQcml2YXRUdWxhamRvbm9zID0gc3RyaV9sZW5ndGgoQWt0aXZUdWxhamRvbm9zU3phbSkgPj0gMQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiBzdHJpX2xlbmd0aChBa3RpdlByaXZhdFR1bGFqZG9ub3MpID49IDEsCiAgICAgICAgICAgICAgICAgdmFsaWRfQWt0aXZQcml2YXRUdWxhamRvbm9zID0gQWt0aXZUdWxhamRvbm9zU3phbSA+PSBBa3RpdlByaXZhdFR1bGFqZG9ub3MsCiAgICAgICAgICAgICAgICAgZmlsbGVkX0NlZ2Zvcm1hID0gc3RyaV9sZW5ndGgoQ2VnZm9ybWEpID49IDEsCiAgICAgICAgICAgICAgICAgZmlsbGVkX0FrdGl2VHVsYWpkb25vc1N6YW1fYW5kX0FrdGl2UHJpdmF0VHVsYWpkb25vc19hbmRfQ2VnZm9ybWEgPSBzdHJpX2xlbmd0aChBa3RpdlR1bGFqZG9ub3NTemFtKSA+PSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIHN0cmlfbGVuZ3RoKEFrdGl2UHJpdmF0VHVsYWpkb25vcykgPj0gMQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiBzdHJpX2xlbmd0aChDZWdmb3JtYSkgPj0gMQogICAgICAgICAgICAgICAgICkKZHQgPC0gZGF0YS50YWJsZShzdW1tYXJ5KGN0KSkKZHQgPC0gZHRbLCBjKDE6NSwgOCldCmBgYAoKbGV0J3Mgc2VlIHRoZSB2YWxpZGl0eSBvZiB0aGUgZGF0YQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpwYW5kZXIoZHQsIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCmRlYWxsb2NhdGluZyBtZW1vcnkhIQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpybShjb21wYW55X2xpc3RfMSkKYGBgCgpTb21lIENlZ2Zvcm1hIHZhbHVlcyBhcmUgbWlzc2luZyAvIEkgaW1wdXRlIHRoZW0gd2l0aCB0aGUgbWVkaWFuIHZhbHVlIGluIGVhY2ggTWVneWUgd2l0aCBzYW1lIEZvdGV2ZWtlbnlzZWdUZXh0IGFuZCBBRkFLb2QKCnN1YnNldHRpbmcgaW50byBkdApgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpkdCA8LSBjb3B5KGRhdGEudGFibGUoY29tcGFueV9saXN0XzJbIWlzLm5hKEFkb3N6YW0pCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiAhaXMubmEoQWt0aXZQcml2YXRUdWxhamRvbm9zKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgIWlzLm5hKEFrdGl2VHVsYWpkb25vc1N6YW0pCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiAhaXMubmEoTWVneWUpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiAhaXMubmEoRm90ZXZla2VueXNlZ1RleHQpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJiAhaXMubmEodW5yZWdfZW1wKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgIWlzLm5hKEFGQUtvZCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC4oQWRvc3phbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENlZ2Zvcm1hLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQWt0aXZQcml2YXRUdWxhamRvbm9zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQWt0aXZUdWxhamRvbm9zU3phbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1lZ3llLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRm90ZXZla2VueXNlZ1RleHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1bnJlZ19lbXAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBRkFLb2QpXSkpCmBgYAoKZmFjdG9yaXphdGlvbiBvZiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZHRbLCAnOj0nIChDZWdmb3JtYSA9IGFzLmZhY3RvcihDZWdmb3JtYSksCiAgICAgICAgICAgTWVneWUgPSBhcy5mYWN0b3IoTWVneWUpLAogICAgICAgICAgIEZvdGV2ZWtlbnlzZWdUZXh0ID0gYXMuZmFjdG9yKEZvdGV2ZWtlbnlzZWdUZXh0KSwKICAgICAgICAgICB1bnJlZ19lbXAgPSBhcy5mYWN0b3IodW5yZWdfZW1wKSwKICAgICAgICAgICBBRkFLb2QgPSBhcy5mYWN0b3IoQUZBS29kKQogICAgICAgICAgICldCmBgYAoKaW1wdXRhdGlvbiBvZiBDZWdmb3JtYQpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmb3JtdWxhIDwtIENlZ2Zvcm1hIH4gQWt0aXZQcml2YXRUdWxhamRvbm9zICsgQWt0aXZUdWxhamRvbm9zU3phbSArIE1lZ3llICsgRm90ZXZla2VueXNlZ1RleHQgKyB1bnJlZ19lbXAgKyBBRkFLb2QKZHQgPC0gZGF0YS50YWJsZShpbXB1dGVfbWYoZHQsIGZvcm11bGEpKQpgYGAKCmpvaW5pbmcKYGBge3IsIHdhcm5pbmc9RkFMU0UsIGluY2x1ZGU9VFJVRX0KZHQgPC0gZHRbLCAuKEFkb3N6YW0sIENlZ2Zvcm1hKV0Kc2V0a2V5KGR0LCBBZG9zemFtKQpzZXRrZXkoY29tcGFueV9saXN0XzIsIEFkb3N6YW0pCmNvbXBhbnlfbGlzdF8yIDwtIGRhdGEudGFibGUoZHRbY29tcGFueV9saXN0XzJdKQpjb21wYW55X2xpc3RfMltpcy5uYShDZWdmb3JtYSksIENlZ2Zvcm1hIDo9IGkuQ2VnZm9ybWFdCmNvbXBhbnlfbGlzdF8yWywgaS5DZWdmb3JtYSA6PSBOVUxMXQpgYGAKCnNhdmluZwpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvX3RtcC9jb21wYW55X2xpc3RfMi5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUoY29tcGFueV9saXN0XzIsIAogICAgICAgZmlsZSA9IGZpbGVfb3V0LAogICAgICAgblRocmVhZCA9IGdldERUdGhyZWFkcygpKQpgYGAKIApsZXQncyBjaGVjayB0aGUgaW1wdXRhdGlvbiBhZ2FpbgpgYGB7ciwgd2FybmluZz1GQUxTRSwgaW5jbHVkZT1UUlVFfQpjdCA8LSBjaGVja190aGF0KGNvbXBhbnlfbGlzdF8yLAogICAgICAgICAgICAgICAgIGZpbGxlZF9Ba3RpdlR1bGFqZG9ub3NTemFtID0gc3RyaV9sZW5ndGgoQWt0aXZUdWxhamRvbm9zU3phbSkgPj0gMSwKICAgICAgICAgICAgICAgICBmaWxsZWRfQWt0aXZQcml2YXRUdWxhamRvbm9zID0gc3RyaV9sZW5ndGgoQWt0aXZQcml2YXRUdWxhamRvbm9zKSA+PSAxLAogICAgICAgICAgICAgICAgIGZpbGxlZF9Ba3RpdlR1bGFqZG9ub3NTemFtX2FuZF9Ba3RpdlByaXZhdFR1bGFqZG9ub3MgPSBzdHJpX2xlbmd0aChBa3RpdlR1bGFqZG9ub3NTemFtKSA+PSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIHN0cmlfbGVuZ3RoKEFrdGl2UHJpdmF0VHVsYWpkb25vcykgPj0gMSwKICAgICAgICAgICAgICAgICB2YWxpZF9Ba3RpdlByaXZhdFR1bGFqZG9ub3MgPSBBa3RpdlR1bGFqZG9ub3NTemFtID49IEFrdGl2UHJpdmF0VHVsYWpkb25vcywKICAgICAgICAgICAgICAgICBmaWxsZWRfQ2VnZm9ybWEgPSBzdHJpX2xlbmd0aChDZWdmb3JtYSkgPj0gMSwKICAgICAgICAgICAgICAgICBmaWxsZWRfQWt0aXZUdWxhamRvbm9zU3phbV9hbmRfQWt0aXZQcml2YXRUdWxhamRvbm9zX2FuZF9DZWdmb3JtYSA9IHN0cmlfbGVuZ3RoKEFrdGl2VHVsYWpkb25vc1N6YW0pID49IDEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICYgc3RyaV9sZW5ndGgoQWt0aXZQcml2YXRUdWxhamRvbm9zKSA+PSAxCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAmIHN0cmlfbGVuZ3RoKENlZ2Zvcm1hKSA+PSAxCiAgICAgICAgICAgICAgICAgKQp0ZW1wIDwtIGRhdGEudGFibGUoc3VtbWFyeShjdCkpCnRlbXAgPC0gdGVtcFssIGMoMTo1LCA4KV0KYGBgCgpsZXQncyBzZWUgdGhlIHZhbGlkaXR5IG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPVRSVUV9CnBhbmRlcih0ZW1wLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgpMZXQncyB0YWtlIGEgbG9vayBhdCBoaXN0b2dyYW1zIG9mIGNvbHVtbnMgKHZhcmlhYmxlcykgb2YgY29tcGFueSBsaXN0IDIKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcDExIDwtIGdncGxvdChjb21wYW55X2xpc3RfMikgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IGxvZzEwKFNhamF0VG9rZSArIDEpKSwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gJ2JsdWUnLCAKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgICAgICAgYmlud2lkdGggPSAwLjMsCiAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsgCiAgICAgICAgZ2d0aXRsZSgnSGlzdG9ncmFtIGZvciBsb2cxMF9TYWphdFRva2UnKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIHhsYWIoJ2xvZzEwX1NhamF0VG9rZScpICsKICAgICAgICB0aGVtZV9pZ3JheSgpClN5cy5zbGVlcCgyKSAgCgpwMTIgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gbG9nMTAoTWVybGVnRXJlZG1lbnkgKyAxKSksCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICdncmVlbicsIAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gJ2lkZW50aXR5JywKICAgICAgICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDAuMywKICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IEkoMC43KSkgKyAKICAgICAgICBnZ3RpdGxlKCdIaXN0b2dyYW0gZm9yIGxvZzEwX01lcmxlZ0VyZWRtZW55JykgKwogICAgICAgIGZhY2V0X2dyaWQoQmFua2lVZ3lmZWwgfiAuLCBzY2FsZXMgPSAnZnJlZScsIGxhYmVsbGVyID0gbGFiZWxsZXIoQmFua2lVZ3lmZWwgPSBjKCdUUlVFJyA9ICdCYW5raSBVZ3lmZWwnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRkFMU0UnID0gJ05vdCBCYW5raSBVZ3lmZWwnKSkpICsKICAgICAgICB4bGFiKCdsb2cxMF9NZXJsZWdFcmVkbWVueScpICsKICAgICAgICB0aGVtZV9pZ3JheSgpClN5cy5zbGVlcCgyKSAgCgpwMTMgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gbG9nMTAoQXJiZXZldGVsICsgMSkpLAogICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSAnb3JhbmdlJywgCiAgICAgICAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMC4zLAogICAgICAgICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArIAogICAgICAgIGdndGl0bGUoJ0hpc3RvZ3JhbSBmb3IgbG9nMTBfQXJiZXZldGVsJykgKwogICAgICAgIGZhY2V0X2dyaWQoQmFua2lVZ3lmZWwgfiAuLCBzY2FsZXMgPSAnZnJlZScsIGxhYmVsbGVyID0gbGFiZWxsZXIoQmFua2lVZ3lmZWwgPSBjKCdUUlVFJyA9ICdCYW5raSBVZ3lmZWwnLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnRkFMU0UnID0gJ05vdCBCYW5raSBVZ3lmZWwnKSkpICsKICAgICAgICB4bGFiKCdsb2cxMF9BcmJldmV0ZWwnKSArCiAgICAgICAgdGhlbWVfaWdyYXkoKQpTeXMuc2xlZXAoMikKCnAxNCA8LSBnZ3Bsb3QoY29tcGFueV9saXN0XzIpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBsb2cxMChBa3RpdlByaXZhdFR1bGFqZG9ub3MgKzEpKSwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gJ3JlZCcsIAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gJ2lkZW50aXR5JywKICAgICAgICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDAuMSwKICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IEkoMC43KSkgKyAKICAgICAgICBnZ3RpdGxlKCdIaXN0b2dyYW0gZm9yIGxvZzEwX0FrdGl2UHJpdmF0VHVsYWpkb25vcycpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgeGxhYignbG9nMTBfQWt0aXZQcml2YXRUdWxhamRvbm9zJykgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwMTUgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gQWt0aXZUdWxhamRvbm9zU3phbSksCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICdncmVlbicsIAogICAgICAgICAgICAgICAgICAgICAgIHBvc2l0aW9uID0gJ2lkZW50aXR5JywKICAgICAgICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDAuMSwKICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IEkoMC43KSkgKyAKICAgICAgICBnZ3RpdGxlKCdIaXN0b2dyYW0gZm9yIGxvZzEwX0FrdGl2VHVsYWpkb25vc1N6YW0nKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIHhsYWIoJ2xvZzEwX0FrdGl2VHVsYWpkb25vc1N6YW0nKSArCiAgICAgICAgc2NhbGVfeF9sb2cxMCgpICsKICAgICAgICB0aGVtZV9pZ3JheSgpClN5cy5zbGVlcCgyKQoKcDE2IDwtIGdncGxvdChjb21wYW55X2xpc3RfMikgKyAKICAgICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeCA9IFR1bGFqQ2VnZXNCYW5raVVneWZlbE5yKSwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gJ2Jyb3duJywKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgICAgICAgI2JpbndpZHRoID0gMSwKICAgICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IEkoMC43KSkgKwogICAgICAgIGdndGl0bGUoJ0hpc3RvZ3JhbSBmb3IgVHVsYWpDZWdlc0JhbmtpVWd5ZmVsTnInKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIHhsYWIoJ1R1bGFqQ2VnZXNCYW5raVVneWZlbE5yJykgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwMTcgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArIAogICAgICAgIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gbG9nMTAoQ2Vna29yYSArIDEpKSwKICAgICAgICAgICAgICAgICAgICAgICBmaWxsID0gJ2toYWtpJywKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgICAgICAgYmlud2lkdGggPSAwLjIsCiAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBnZ3RpdGxlKCdIaXN0b2dyYW0gZm9yIGxvZzEwX0NlZ2tvcmEnKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIHhsYWIoJ2xvZzEwX0NlZ2tvcmEnKSArCiAgICAgICAgdGhlbWVfaWdyYXkoKQpTeXMuc2xlZXAoMikKCnAxOCA8LSBnZ3Bsb3QoY29tcGFueV9saXN0XzIpICsgCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBsb2cxMChMZWdrb3pGaW9rVGF2UHJveHkgKyAxKSksCiAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9ICdtYWdlbnRhJywKICAgICAgICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgICAgICAgYmlud2lkdGggPSAwLjIsCiAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBnZ3RpdGxlKCdIaXN0b2dyYW0gZm9yIGxvZzEwX0xlZ2tvekZpb2tUYXZQcm94eScpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgeGxhYignbG9nMTBfTGVna296Rmlva1RhdlByb3h5JykgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwMTkgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArCiAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBhcy5mYWN0b3IoQ2VnZm9ybWEpKSwKICAgICAgICAgICAgICAgICBmaWxsID0gJ2Jyb3duJywKICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgZ2d0aXRsZSgnQnJlYWtkb3duIG9mIENlZ2Zvcm1hJykgKwogICAgICAgIHhsYWIoJ0NlZ2Zvcm1hJykgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwMjAgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArCiAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBhcy5mYWN0b3IoTkFWVGVydWxldEtvZCkpLAogICAgICAgICAgICAgICAgIGZpbGwgPSAncHVycGxlJywKICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgZ2d0aXRsZSgnQnJlYWtkb3duIG9mIE5BVlRlcnVsZXRLb2QnKSArCiAgICAgICAgeGxhYignTkFWVGVydWxldEtvZCcpICsKICAgICAgICB0aGVtZV9pZ3JheSgpClN5cy5zbGVlcCgyKQoKcDIxIDwtIGdncGxvdChjb21wYW55X2xpc3RfMikgKwogICAgICAgIGdlb21fYmFyKGFlcyh4ID0gYXMuZmFjdG9yKE1lZ3llKSksCiAgICAgICAgICAgICAgICAgZmlsbCA9ICdwaW5rJywKICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgZ2d0aXRsZSgnQnJlYWtkb3duIG9mIE1lZ3llJykgKwogICAgICAgIHhsYWIoJ01lZ3llJykgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwMjIgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArCiAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBhcy5mYWN0b3IoVmFyb3MpKSwKICAgICAgICAgICAgICAgICBmaWxsID0gJ2dyZWVuJywKICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgZ2d0aXRsZSgnQnJlYWtkb3duIG9mIFZhcm9zJykgKwogICAgICAgIHhsYWIoJ1Zhcm9zJykgKwogICAgICAgIHRoZW1lX2lncmF5KCkKU3lzLnNsZWVwKDIpCgpwMjMgPC0gZ2dwbG90KGNvbXBhbnlfbGlzdF8yKSArCiAgICAgICAgZ2VvbV9iYXIoYWVzKHggPSBhcy5mYWN0b3IodW5yZWdfZW1wKSksCiAgICAgICAgICAgICAgICAgZmlsbCA9ICdibHVlJywKICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgZ2d0aXRsZSgnQnJlYWtkb3duIG9mIHVucmVnX2VtcCcpICsKICAgICAgICB4bGFiKCd1bnJlZ19lbXAnKSArCiAgICAgICAgdGhlbWVfaWdyYXkoKQpTeXMuc2xlZXAoMikKCnAyNCA8LSBnZ3Bsb3QoY29tcGFueV9saXN0XzIpICsKICAgICAgICBnZW9tX2JhcihhZXMoeCA9IGFzLmZhY3RvcihGb3RldmVrZW55c2VnVGV4dCkpLAogICAgICAgICAgICAgICAgIGZpbGwgPSAnbWFnZW50YScsCiAgICAgICAgICAgICAgICAgcG9zaXRpb24gPSAnaWRlbnRpdHknLAogICAgICAgICAgICAgICAgIGFscGhhID0gSSgwLjcpKSArCiAgICAgICAgZmFjZXRfZ3JpZChCYW5raVVneWZlbCB+IC4sIHNjYWxlcyA9ICdmcmVlJywgbGFiZWxsZXIgPSBsYWJlbGxlcihCYW5raVVneWZlbCA9IGMoJ1RSVUUnID0gJ0JhbmtpIFVneWZlbCcsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdGQUxTRScgPSAnTm90IEJhbmtpIFVneWZlbCcpKSkgKwogICAgICAgIGdndGl0bGUoJ0JyZWFrZG93biBvZiBGb3RldmVrZW55c2VnVGV4dCcpICsKICAgICAgICB4bGFiKCdGb3RldmVrZW55c2VnVGV4dCcpICsKICAgICAgICB0aGVtZV9pZ3JheSgpClN5cy5zbGVlcCgyKQoKcDI1IDwtIGdncGxvdChjb21wYW55X2xpc3RfMikgKwogICAgICAgIGdlb21fYmFyKGFlcyh4ID0gYXMuZmFjdG9yKEFGQUtvZCkpLAogICAgICAgICAgICAgICAgIGZpbGwgPSAnb3JhbmdlJywKICAgICAgICAgICAgICAgICBwb3NpdGlvbiA9ICdpZGVudGl0eScsCiAgICAgICAgICAgICAgICAgYWxwaGEgPSBJKDAuNykpICsKICAgICAgICBmYWNldF9ncmlkKEJhbmtpVWd5ZmVsIH4gLiwgc2NhbGVzID0gJ2ZyZWUnLCBsYWJlbGxlciA9IGxhYmVsbGVyKEJhbmtpVWd5ZmVsID0gYygnVFJVRScgPSAnQmFua2kgVWd5ZmVsJywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ0ZBTFNFJyA9ICdOb3QgQmFua2kgVWd5ZmVsJykpKSArCiAgICAgICAgZ2d0aXRsZSgnQnJlYWtkb3duIG9mIEFGQUtvZCcpICsKICAgICAgICB4bGFiKCdBRkFLb2QnKSArCiAgICAgICAgdGhlbWVfaWdyYXkoKQpTeXMuc2xlZXAoMikKYGBgCgp3aXRob3V0IHBsb3RseSBpbiBvbmUgZ3JpZDoKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBtdWx0aXBsb3QocDExLCBwMTIsIHAxMywgcDE0LCBwMTUsIHAxNiwgcDE3LCBwMTgsIHAxOSwgcDIwLCBwMjEsIHAyMiwgcDIzLCBwMjQsIHAyNSwgY29scyA9IDIpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxMSA8LSBnZ3Bsb3RseShwMTEpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxMS5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDExKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxMiA8LSBnZ3Bsb3RseShwMTIpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxMi5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDEyKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxMyA8LSBnZ3Bsb3RseShwMTMpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxMy5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDEzKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxNCA8LSBnZ3Bsb3RseShwMTQpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxNC5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDE0KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxNSA8LSBnZ3Bsb3RseShwMTUpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxNS5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDE1KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxNiA8LSBnZ3Bsb3RseShwMTYpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxNi5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDE2KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxNyA8LSBnZ3Bsb3RseShwMTcpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxNy5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDE3KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxOCA8LSBnZ3Bsb3RseShwMTgpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxOC5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDE4KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAxOSA8LSBnZ3Bsb3RseShwMTkpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAxOS5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDE5KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAyMCA8LSBnZ3Bsb3RseShwMjApClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAyMC5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDIwKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAyMSA8LSBnZ3Bsb3RseShwMjEpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAyMS5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDIxKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAyMiA8LSBnZ3Bsb3RseShwMjIpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAyMi5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDIyKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAyMyA8LSBnZ3Bsb3RseShwMjMpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAyMy5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDIzKSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAyNCA8LSBnZ3Bsb3RseShwMjQpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAyNC5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDI0KSwgZmlsZV9vdXQpCmBgYAoKdHJhbnNmb3JtaW5nIGludG8gaW50ZXJhY3RpdmUgZ3JhcGgKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHAyNSA8LSBnZ3Bsb3RseShwMjUpClN5cy5zbGVlcCgyKQpgYGAKCnNhdmluZwpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvcHAyNS5odG1sJywgc2VwID0gJycpCmh0bWx3aWRnZXRzOjpzYXZlV2lkZ2V0KGFzLndpZGdldChwcDI1KSwgZmlsZV9vdXQpCmBgYAoKcGxvdHRpbmcKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGF5b3V0KHBwMTEsIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHAxMiwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDEzLCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKbGF5b3V0KHBwMTQsIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHAxNSwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDE2LCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKbGF5b3V0KHBwMTcsIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHAxOCwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDE5LCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKbGF5b3V0KHBwMjAsIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHAyMSwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDIyLCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKbGF5b3V0KHBwMjMsIGRyYWdtb2RlID0gJ3BhbicpClN5cy5zbGVlcCgyKQpsYXlvdXQocHAyNCwgZHJhZ21vZGUgPSAncGFuJykKU3lzLnNsZWVwKDIpCmxheW91dChwcDI1LCBkcmFnbW9kZSA9ICdwYW4nKQpTeXMuc2xlZXAoMikKYGBgCgptb3ZpbmcgdGhvc2UgcGxvdGx5IGZpbGVzIGludG8gdG1wIGZvbGRlcgpgYGB7ciwgaW5jbHVkZT1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpmaWxleiA8LSBsaXN0LmZpbGVzKGdldHdkKCksIHBhdHRlcm4gPSAnXnBwXFxkK1xcLmh0bWwkJykKc2FwcGx5KGZpbGV6LCBGVU4gPSBmdW5jdGlvbihlYWNoUGF0aCkgewogIGZpbGUucmVuYW1lKGZyb20gPSBlYWNoUGF0aCwKICAgICAgICAgICAgICB0byA9IHN1YihwYXR0ZXJuID0gJ3BwJywKICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlbWVudCA9ICcuL190bXAvcHAnLAogICAgICAgICAgICAgICAgICAgICAgIGVhY2hQYXRoKSkKICB9KQpgYGAKCiBkZWFsbG9jYXRpbmcgbWVtb3J5ISEKYGBge3IsIGluY2x1ZGU9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kcm0ocDEsIHAyLCBwMywgcDQsIHA1LCBwNiwgcDcsIHA4LCBwOSwgcDEwLCBwMTEsIHAxMiwgcDEzLCBwMTQsIHAxNSwgcDE2LCBwMTcsIHAxOCwgcDE5LCBwMjAsIHAyMSwgcDIyLCBwMjMsIHAyNCwgcDI1KQpybShwcDEsIHBwMiwgcHAzLCBwcDQsIHBwNSwgcHA2LCBwcDcsIHBwOCwgcHA5LCBwcDEwLCBwcDExLCBwcDEyLCBwcDEzLCBwcDE0LCBwcDE1LCBwcDE2LCBwcDE3LCBwcDE4LCBwcDE5LCBwcDIwLCBwcDIxLCBwcDIyLCBwcDIzLCBwcDI0LCBwcDI1KQpgYGAKCkFjY29yZGluZyB0byBjb21wYXJlZCBkaXN0cmlidXRpb25zLCB0aGVzZSBtYXkgYmUgcHJlZGljdG9yIHZhcmlhYmxlczoKU2FqYXRUb2tlCk1lcmxlZ0VyZWRtZW55CkFrdGl2VHVsYWpkb25vc1N6YW0KVHVsYWpDZWdlc0JhbmtpVWd5ZmVsTnIKQ2Vna29yYQpMZWdrb3pGaW9rVGF2UHJveHkKQ2VnZm9ybWEKTkFWVGVydWxldEtvZApNZWd5ZQpWYXJvcwpGb3RldmVrZW55c2VnVGV4dApBRkFLb2QKCkkgcHV0IHRoZW0gYW5kIHRoZSB0YXJnZXQgdmFyaWFibGUgKEJhbmtpVWd5ZmVsKSBpbnRvIGEgbmV3IGRhdGEgdGFibGUgYW5kIGJsZW5kZWQgTkFzLgoKSSBvbWl0dGVkIHRoZSBWYXJvcyB2YXJpYWJsZSwgYmVjYXVzZSBJIHRoaW5rIHRoYXQgaXMgdG9vIHJlc3RyaWN0aXZlLgpgYGB7ciwgd2FybmluZz1GQUxTRSxpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdF8zIDwtIGNvcHkoZGF0YS50YWJsZShjb21wYW55X2xpc3RfMlshaXMubmEoQmFua2lVZ3lmZWwpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShTYWphdFRva2UpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShNZXJsZWdFcmVkbWVueSkgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKEFrdGl2VHVsYWpkb25vc1N6YW0pICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShUdWxhakNlZ2VzQmFua2lVZ3lmZWxOcikgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKENlZ2tvcmEpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShMZWdrb3pGaW9rVGF2UHJveHkpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShDZWdmb3JtYSkgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKE5BVlRlcnVsZXRLb2QpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICFpcy5uYShNZWd5ZSkgJgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIWlzLm5hKEZvdGV2ZWtlbnlzZWdUZXh0KSAmCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAhaXMubmEoQUZBS29kKSwgLihCYW5raVVneWZlbCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNhamF0VG9rZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1lcmxlZ0VyZWRtZW55LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQWt0aXZUdWxhamRvbm9zU3phbSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFR1bGFqQ2VnZXNCYW5raVVneWZlbE5yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ2Vna29yYSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIExlZ2tvekZpb2tUYXZQcm94eSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENlZ2Zvcm1hLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTkFWVGVydWxldEtvZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE1lZ3llLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRm90ZXZla2VueXNlZ1RleHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBRkFLb2QpXSkpCmBgYAoKbWFraW5nIGZhY3RvcnMgZnJvbSB0ZXh0IHZhcmlhYmxlcwpgYGB7ciwgd2FybmluZz1GQUxTRSxpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdF8zWywgJzo9JyAoTkFWVGVydWxldEtvZCA9IGFzLmZhY3RvcihOQVZUZXJ1bGV0S29kKSwgTWVneWUgPSBhcy5mYWN0b3IoTWVneWUpLCBGb3RldmVrZW55c2VnVGV4dCA9IGFzLmZhY3RvcihGb3RldmVrZW55c2VnVGV4dCksIEFGQUtvZCA9YXMuZmFjdG9yKEFGQUtvZCkpXQpwYW5kZXIoc3RyKGNvbXBhbnlfbGlzdF8zKSkKYGBgCgpzYXZpbmcKYGBge3IsIHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1UUlVFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvX3RtcC9jb21wYW55X2xpc3RfMy5SRGF0YScsIHNlcCA9ICcnKQpmd3JpdGUoY29tcGFueV9saXN0XzMsIAogICAgICAgZmlsZSA9IGZpbGVfb3V0LAogICAgICAgblRocmVhZCA9IGdldERUdGhyZWFkcygpKQpgYGAKCmxldCdzIGNoZWNrIGEgc2FtcGxlIG9mIHRoZSBkYXRhCmBgYHtyLCB3YXJuaW5nPUZBTFNFLGluY2x1ZGU9VFJVRX0KcGFuZGVyKGNvbXBhbnlfbGlzdF8zW3NhbXBsZSguTiwgNSldLCBzcGxpdC50YWJsZSA9IDIwMCkKYGBgCgpkZWFsbG9jYXRpbmcgbWVtb3J5ISEKYGBge3IsIHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1UUlVFfQpybShjb21wYW55X2xpc3RfMikKYGBgCgoKIyBNb2RlbCBidWlsZGluZwoKc3BsaXR0aW5nIGludG8gdHJhaW4sIHRlc3QgJiB2YWxpZGF0aW9uIHN1YnNldApgYGB7ciwgd2FybmluZz1GQUxTRSxpbmNsdWRlPVRSVUV9CmNvbXBhbnlfbGlzdF8zIDwtIGRhdGEudGFibGUoZnJlYWQocGFzdGUoZ2V0d2QoKSwgJy9fdG1wL2NvbXBhbnlfbGlzdF8zLlJEYXRhJywgc2VwID0gJycpKSkKc2V0LnNlZWQoMjAxNDA0MDcpCgpuIDwtIG5yb3coY29tcGFueV9saXN0XzMpCgppZHhfdHJhaW4gPC0gc2FtcGxlKDE6biwgMC41Km4pCmlkeF90ZXN0IDwtIHNhbXBsZShzZXRkaWZmKDE6biwgaWR4X3RyYWluKSwgMC4yNSpuKQppZHhfdmFsaWQgPC0gc2FtcGxlKHNldGRpZmYoc2V0ZGlmZigxOm4sIGlkeF90cmFpbiksIGlkeF90ZXN0KSwgMC4yNSpuKQoKZF90cmFpbiA8LSBjb21wYW55X2xpc3RfM1tpZHhfdHJhaW4sXQpkX3Rlc3QgPC0gY29tcGFueV9saXN0XzNbaWR4X3Rlc3QsXQpkX3ZhbGlkIDwtIGNvbXBhbnlfbGlzdF8zW2lkeF92YWxpZCxdCgpwYW5kb2MuaGVhZGVyKHBhc3RlKCdzaXplIG9mIHRyYWluIHN1YnNldDogJywgZGltKGRfdHJhaW4pWzFdLCBzZXAgPSAnJykpCnBhbmRvYy5oZWFkZXIocGFzdGUoJ3NpemUgb2YgdGVzdCBzdWJzZXQ6ICcsIGRpbShkX3Rlc3QpWzFdLCBzZXAgPSAnJykpCnBhbmRvYy5oZWFkZXIocGFzdGUoJ3NpemUgb2YgdGVzdCBzdWJzZXQ6ICcsIGRpbShkX3ZhbGlkKVsxXSwgc2VwID0gJycpKQpgYGAKCmRlYWxsb2NhdGluZyBtZW1vcnkKYGBge3IsIHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1UUlVFfQpybShjb21wYW55X2xpc3RfMykKYGBgCgpJIGNob3NlIHJhbmRvbSBmb3Jlc3QgZGVjaXNpb24gdHJlZSBmb3IgcHJlZGljdGluZyB0YXJnZXRhYmxlIGNvbXBhbmllcyBhbmQgSSB1c2VkIGgybyBzaW5jZSBpdCBpcyBmYXN0ICYgcG93ZXJmdWwgLgpSRiBpcyBhIHN3aXNzLWFybXkta25pZmUgbWV0aG9kIGZvciBjbGFzc2lmaWNhdGlvbi4gSXQgbWVhbnMgYm9vc3RyYXBwaW5nIGRhdGEsIGJ1aWxkaW5nIHRyZWVzLCBhZ2dyZWdhdGluZyB3aXRoIHJhbmRvbSBzdWJzZXQgb2YgdmFyaWFibGUgYXQgZWFjaCBzcGxpdC4KCmNvbXB1dGluZyBvcHRpbWFsIG1lbW9yeSBsZXZlbCBmb3IgaDJvCmBgYHtyLCB3YXJuaW5nPUZBTFNFLGluY2x1ZGU9VFJVRX0KbWVtIDwtIHBhc3RlKGFzLmNoYXJhY3Rlcihyb3VuZChhcy5udW1lcmljKHN5c3RlbSgiYXdrICcvTWVtLyB7cHJpbnQgJDJ9JyAvcHJvYy9tZW1pbmZvIiwgaW50ZXJuPVRSVUUpKVsxXSowLjgvMTAyNC8xMDI0LCAwKSksICdnJywgc2VwID0gJycpCmBgYAoKaW5pdGlhbGl6ZSBoMm8gSmF2YSBzZXJ2ZXIgKFIgY29ubmVjdHMgdmlhIFJFU1QpIC0gc2V0dGluZyBSQU0gc2l6ZSAmIHRocmVhZCBudW1iZXJzIHRvIG1heGltdW0gYXZhaWxhYmxlCmBgYHtyLCB3YXJuaW5nPUZBTFNFLGluY2x1ZGU9VFJVRX0KaDJvLmluaXQobWF4X21lbV9zaXplID0gbWVtLAogICAgICAgICBudGhyZWFkcyA9IC0xKQpgYGAKCnVwbG9hZGluZyBkYXRhIHRvIEgyTwpgYGB7ciwgd2FybmluZz1GQUxTRSxpbmNsdWRlPVRSVUV9CmRoMm9fdHJhaW4gPC0gYXMuaDJvKGRfdHJhaW4pCmRoMm9fdGVzdCA8LSBhcy5oMm8oZF90ZXN0KQpkaDJvX3ZhbGlkIDwtIGFzLmgybyhkX3ZhbGlkKQpgYGAKCm1hY2hpbmUgbGVhcm5pbmcKSSBwbGF5ZWQgYXJvdW5kIHdpdGggc2V2ZXJhbCBzZXR0aW5ncyBhbmQgdGhlc2Ugc2VlbWVkIGdvb2QgZW5vdWdoCmBgYHtyLCB3YXJuaW5nPUZBTFNFLGluY2x1ZGU9VFJVRX0KbW9kZWwgPC0gaDJvLnJhbmRvbUZvcmVzdCh4ID0gMjpuY29sKGRoMm9fdHJhaW4pLCAjIHByZWRpY3RvciB2YXJpYWJsZXMKICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gMSwgIyB0YXJnZXQgdmFyaWFibGUKICAgICAgICAgICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSA9IGRoMm9fdHJhaW4sICMgc3BlYWtzIGZvciBpdHNlbGYKICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbF9pZCA9ICdCYW5raVVneWZlbE1vZGVsJywgIyBiZWluZyBuYW1lIG9mIHRoZSBtb2RlbAogICAgICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IDUsICMgbnIgb2YgZm9sZHMgZm9yIE4tZm9sZCBjcm9zcy12YWxpZGF0aW9uCiAgICAgICAgICAgICAgICAgICAgICAgICAgbXRyaWVzID0gLTEsICMgbnIgb2YgdmFyaWFibGVzIHJhbmRvbWx5IHNhbXBsZWQgYXMgY2FuZGlkYXRlcyBhdCBlYWNoIHNwbGl0LiBzcXJ0KCMgb2YgcHJlZGljdG9ycykKICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZXMgPSAzMCwgIyBuciBvZiB0cmVlcwogICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9kZXB0aCA9IDUgIyBtYXhpbXVtIHRyZWUgZGVwdGgKICAgICAgICAgICAgICAgICAgICAgICAgICApCmBgYAoKZGVhbGxvY2F0aW5nIG1lbW9yeQpgYGB7ciwgd2FybmluZz1GQUxTRSxpbmNsdWRlPVRSVUV9CnJtKGRfdHJhaW4sIGRfdGVzdCkKYGBgCgpMZXQncyBzZWUgaG93IGl0IHBlcmZvcm1zLgpPdXIgZ29hbCBpcyB0byBtYXhpbWl6ZSBtb2RlbCBhY2N1cmFjeSBidXQgYXZvaWRpbmcgb3ZlcmZpdHRpbmcgYXQgdGhlIHNhbWUgdGltZS4KCkFyZWEgVW5kZXIgdGhlIEN1cnZlIApwcmVwYXJhdGlvbiBmb3IgcGxvdHRpbmcgUk9DIGZvciB0cmFpbmluZyBkYXRhc2V0CmBgYHtyLCB3YXJuaW5nPUZBTFNFLGluY2x1ZGU9VFJVRSxtZXNzYWdlPUZBTFNFfQpkdDIgPC0gZGF0YS50YWJsZShoMm8ucGVyZm9ybWFuY2UobW9kZWwsIGRoMm9fdHJhaW4pQG1ldHJpY3MkdGhyZXNob2xkc19hbmRfbWV0cmljX3Njb3Jlc1sxODoxOV0pCgpwMjYgPC0gZ2dwbG90KGR0MiwgYWVzKHggPSBmcHIsIHkgPSB0cHIpKSArCiAgICAgICAgICBnZW9tX3BvaW50KGNvbG9yID0gJ2JsdWUnLAogICAgICAgICAgICAgICAgICAgICBwY2ggPSAxLAogICAgICAgICAgICAgICAgICAgICBhbHBoYSA9IDAuNykgKwogICAgICAgICAgY29vcmRfZml4ZWQocmF0aW8gPSAxKSArCiAgICAgICAgICB4bGFiKCdGYWxzZSBQb3NpdGl2ZSBSYXRlJykgKwogICAgICAgICAgeWxhYignVHJ1ZSBQb3NpdGl2ZSBSYXRlJykgKwogICAgICAgICAgZ2d0aXRsZSgnUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIG9mIG1vZGVsIG9uIHRyYW5pbmcgZGF0YXNldCcpICsKICAgICAgICAgIHRoZW1lX2lncmF5KCkKYGBgCgoKc2F2aW5nIFJPQyBmb3IgdHJhaW5pbmcgZGF0YXNldApgYGB7ciwgd2FybmluZz1GQUxTRSxpbmNsdWRlPVRSVUUsbWVzc2FnZT1GQUxTRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL190bXAvJywgc2VwID0gJycpCmdnc2F2ZShwYXRoID0gZmlsZV9vdXQsIGZpbGVuYW1lID0gJ3AyNi5wbmcnKQpgYGAKCgpwbG90dGluZyBST0MgZm9yIHRyYWluaW5nIGRhdGFzZXQKYGBge3IsIHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1UUlVFLG1lc3NhZ2U9RkFMU0V9CnBsb3QocDI2KQpTeXMuc2xlZXAoMikKYGBgCgpBcmVhIFVuZGVyIHRoZSBDdXJ2ZSByZXN1bHRzCllvdSBjYW4gYWxzbyBzZWUvY2hlY2svZXhhbWluZSB0aGUgZGF0YSBvbiB0aGUgaDJvIHVzZXIgaW50ZXJmYWNlOiBodHRwOi8vbG9jYWxob3N0OjU0MzIxIApodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9SZWNlaXZlcl9vcGVyYXRpbmdfY2hhcmFjdGVyaXN0aWMjQXJlYV91bmRlcl90aGVfY3VydmUKYGBge3IsIHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1UUlVFLG1lc3NhZ2U9RkFMU0V9CmF1Y190cmFpbiA8LSByb3VuZChoMm8uYXVjKGgyby5wZXJmb3JtYW5jZShtb2RlbCwgZGgyb190cmFpbikpLCA1KQphdWNfdGVzdCA8LSByb3VuZChoMm8uYXVjKGgyby5wZXJmb3JtYW5jZShtb2RlbCwgZGgyb190ZXN0KSksIDUpCmF1Y192YWxpZCA8LSByb3VuZChoMm8uYXVjKGgyby5wZXJmb3JtYW5jZShtb2RlbCwgZGgyb192YWxpZCkpLCA1KQpwYW5kb2MuaGVhZGVyKCdBcmVhIFVuZGVyIHRoZSBDdXJ2ZSByZXN1bHRzJykKcGFuZG9jLmhlYWRlcignMC45MC0xLjAwID0gZXhjZWxsZW50JykKcGFuZG9jLmhlYWRlcignMC44MC0wLjkwID0gZ29vZCcpCnBhbmRvYy5oZWFkZXIoJzAuNzAtMC44MCA9IGZhaXInKQpwYW5kb2MuaGVhZGVyKCcwLjYwLTAuNzAgPSBwb29yJykKcGFuZG9jLmhlYWRlcignMC41MC0wLjYwID0gZmFpbCcpCnBhbmRvYy5oZWFkZXIoJycpCnBhbmRvYy5oZWFkZXIoJ091ciBBVUMgaXMgZXhjZWxsZW50IGZvciBhbGwgdGhlIHRyYWluLCB0ZXN0IGFuZCB2YWxpZGF0aW9uIGRhdGFzZXQuIEluIGFkZGl0aW9uIHRoZXkgYXJlIHZlcnkgY2xvc2UgdG8gZWFjaCBvdGhlciB3aGljaCBtZWFucyBubyBvdmVyZml0dGluZyEhJykKcGFuZG9jLmhlYWRlcihwYXN0ZSgndHJhaW5pbmcgZGF0YXNldCBBVUM6ICcsIGF1Y190cmFpbiwgc2VwID0gJycpKQpwYW5kb2MuaGVhZGVyKHBhc3RlKCd0ZXN0aW5nIGRhdGFzZXQgQVVDOiAnLCBhdWNfdGVzdCwgc2VwID0gJycpKQpwYW5kb2MuaGVhZGVyKHBhc3RlKCd2YWxpZGF0aW9uIGRhdGFzZXQgQVVDOiAnLCBhdWNfdmFsaWQsIHNlcCA9ICcnKSkKYGBgCgpMZXQgc2VlIHRoZSBjb25mdXNpb24gbWF0cmljZXMKWW91IGNhbiBhbHNvIHNlZS9jaGVjay9leGFtaW5lIHRoZSBkYXRhIG9uIHRoZSBoMm8gdXNlciBpbnRlcmZhY2U6IGh0dHA6Ly9sb2NhbGhvc3Q6NTQzMjEgCmh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NvbmZ1c2lvbl9tYXRyaXgKYGBge3IsIHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1UUlVFLG1lc3NhZ2U9RkFMU0V9CnBhbmRvYy5oZWFkZXIoJyAnKQpwYW5kZXIoJ1RoZSBudW1iZXIgb2YgbWlzc2NsYXNzaWZpZWQgcmVjb3JkcyBhcmUgYmVsb3cgMyUgaW4gZWFjaCBkYXRhc2V0JykKcGFuZG9jLmhlYWRlcignICcpCnBhbmRvYy5oZWFkZXIoJ2NvbmZ1c2lvbiBtYXRyaXggb2YgdHJhaW5pbmcgZGF0YXNldCcpCnBhbmRvYy50YWJsZShoMm8uY29uZnVzaW9uTWF0cml4KG1vZGVsLCBkaDJvX3RyYWluKSkKcGFuZG9jLmhlYWRlcignY29uZnVzaW9uIG1hdHJpeCBvZiB0ZXN0aW5nIGRhdGFzZXQnKQpwYW5kb2MudGFibGUoaDJvLmNvbmZ1c2lvbk1hdHJpeChtb2RlbCwgZGgyb190ZXN0KSkKcGFuZG9jLmhlYWRlcignY29uZnVzaW9uIG1hdHJpeCBvZiB2YWxpZGF0aW9uIGRhdGFzZXQnKQpwYW5kb2MudGFibGUoaDJvLmNvbmZ1c2lvbk1hdHJpeChtb2RlbCwgZGgyb192YWxpZCkpCmBgYAoKYWxsIG1ldHJpY3Mgb2YgdGhlIG1vZGVsCllvdSBjYW4gYWxzbyBzZWUvY2hlY2svZXhhbWluZSB0aGUgZGF0YSBvbiB0aGUgaDJvIHVzZXIgaW50ZXJmYWNlOiBodHRwOi8vbG9jYWxob3N0OjU0MzIxCmBgYHtyLCB3YXJuaW5nPUZBTFNFLGluY2x1ZGU9VFJVRSxtZXNzYWdlPUZBTFNFfQpwYW5kb2MuaGVhZGVyKCcgJykKcGFuZG9jLmhlYWRlcignYWxsIG1ldHJpY3Mgb2YgdGhlIG1vZGVsIGZvciB0cmFpbmluZyBkYXRhc2V0JykKcHJpbnQoaDJvLnBlcmZvcm1hbmNlKG1vZGVsLCBkaDJvX3RyYWluKSkKcGFuZG9jLmhlYWRlcignICcpCnBhbmRvYy5oZWFkZXIoJ2FsbCBtZXRyaWNzIG9mIHRoZSBtb2RlbCBmb3IgdGVzdCBkYXRhc2V0JykKcHJpbnQoaDJvLnBlcmZvcm1hbmNlKG1vZGVsLCBkaDJvX3Rlc3QpKQpwYW5kb2MuaGVhZGVyKCcgJykKcGFuZG9jLmhlYWRlcignYWxsIG1ldHJpY3Mgb2YgdGhlIG1vZGVsIGZvciB2YWxpZGF0aW9uIGRhdGFzZXQnKQpwcmludChoMm8ucGVyZm9ybWFuY2UobW9kZWwsIGRoMm9fdmFsaWQpKQpgYGAKCmRlYWxsb2NhdGluZyBtZW1vcnkKYGBge3IsIHdhcm5pbmc9RkFMU0UsaW5jbHVkZT1UUlVFLG1lc3NhZ2U9RkFMU0V9CnJtKHAyNikKYGBgCgpzYXZpbmcgbW9kZWwgaW4gYmluYXJ5CmBgYHtyLCBpbmNsdWRlPVRSVUUsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvX3Jlc3VsdHMvJywgc2VwID0gJycpCmgyby5zYXZlTW9kZWwobW9kZWwsIHBhdGggPSBmaWxlX291dCkKYGBgCgpzYXZpbmcgbW9kZWwgc2NvcmluZyBpbiBQT0pPCmBgYHtyLCBpbmNsdWRlPVRSVUUsd2FybmluZz1GQUxTRSxtZXNzYWdlPUZBTFNFfQpmaWxlX291dCA9IHBhc3RlKGdldHdkKCksICcvX3Jlc3VsdHMnLCBzZXAgPSAnJykKaDJvLmRvd25sb2FkX3Bvam8obW9kZWwsIHBhdGggPSBmaWxlX291dCwgZ2V0X2phciA9IFRSVUUpCmBgYAoKZ2V0IGZpdHRlZCB2YWx1ZXMgb2YgdGhlIHZhbGlkYXRpb24gZGF0YXNldCAocHVibGljX3Rlc3Rfc2NvcmVkLmNzdikKYGBge3IsIGluY2x1ZGU9VFJVRSx3YXJuaW5nPUZBTFNFLG1lc3NhZ2U9RkFMU0V9CkJhbmtpVWd5ZmVsTW9kZWwuZml0IDwtIGFzLmRhdGEudGFibGUoaDJvLnByZWRpY3Qob2JqZWN0ID0gbW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGRoMm9fdmFsaWQpKVssIDI6M10KY29sbmFtZXMoQmFua2lVZ3lmZWxNb2RlbC5maXQpIDwtIGMoJ3AwJywgJ3AxJykKdGhyaGQgPC0gaDJvLmZpbmRfdGhyZXNob2xkX2J5X21heF9tZXRyaWMoIGgyby5wZXJmb3JtYW5jZShtb2RlbCwgZGgyb192YWxpZCksICdmMScpCkJhbmtpVWd5ZmVsTW9kZWwuZml0WywgcHJlZGljdCA6PSBGQUxTRV0KQmFua2lVZ3lmZWxNb2RlbC5maXRbcDEgPj0gdGhyaGQsIHByZWRpY3QgOj0gVFJVRV0Kc2NvcmVkX3Jlc3VsdCA8LSBjYmluZChkX3ZhbGlkLCBCYW5raVVneWZlbE1vZGVsLmZpdCkKcGFuZGVyKHNjb3JlZF9yZXN1bHRbc2FtcGxlKC5OLCA1KV0sIHNwbGl0LnRhYmxlID0gMjAwKQpgYGAKCnNhdmluZyBwcmVkaWN0ZWQgcmVzdWx0IHNldCAoYmFzZWQgb24gdmFsaWRhdGlvbiBkYXRhKQpgYGB7ciwgaW5jbHVkZT1UUlVFLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KZmlsZV9vdXQgPSBwYXN0ZShnZXR3ZCgpLCAnL19yZXN1bHRzL3Njb3JlZF9yZXN1bHQuUkRhdGEnLCBzZXAgPSAnJykKZndyaXRlKHNjb3JlZF9yZXN1bHQsCiAgICAgICBmaWxlID0gZmlsZV9vdXQsCiAgICAgICBuVGhyZWFkID0gZ2V0RFR0aHJlYWRzKCkpCmBgYAoKZGlzY29ubmVjdGluZyBmcm9tIGgybwpgYGB7ciwgaW5jbHVkZT1UUlVFLHdhcm5pbmc9RkFMU0UsbWVzc2FnZT1GQUxTRX0KaDJvLnNodXRkb3duKHByb21wdCA9IEZBTFNFKQpgYGAKCmRlYWxsb2NhdGluZyBtZW1vcnkhCmBgYHtyLCBpbmNsdWRlPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnJtKGxpc3QgPSBscygpKQpgYGA=